]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets_controller_test.rb
Split and move api changeset test_upload_simple_valid
[rails.git] / test / controllers / api / changesets_controller_test.rb
1 require "test_helper"
2
3 module Api
4   class ChangesetsControllerTest < ActionDispatch::IntegrationTest
5     ##
6     # test all routes which lead to this controller
7     def test_routes
8       assert_routing(
9         { :path => "/api/0.6/changesets", :method => :get },
10         { :controller => "api/changesets", :action => "index" }
11       )
12       assert_routing(
13         { :path => "/api/0.6/changesets.json", :method => :get },
14         { :controller => "api/changesets", :action => "index", :format => "json" }
15       )
16       assert_routing(
17         { :path => "/api/0.6/changesets", :method => :post },
18         { :controller => "api/changesets", :action => "create" }
19       )
20       assert_routing(
21         { :path => "/api/0.6/changeset/1", :method => :get },
22         { :controller => "api/changesets", :action => "show", :id => "1" }
23       )
24       assert_routing(
25         { :path => "/api/0.6/changeset/1.json", :method => :get },
26         { :controller => "api/changesets", :action => "show", :id => "1", :format => "json" }
27       )
28       assert_routing(
29         { :path => "/api/0.6/changeset/1", :method => :put },
30         { :controller => "api/changesets", :action => "update", :id => "1" }
31       )
32
33       assert_recognizes(
34         { :controller => "api/changesets", :action => "create" },
35         { :path => "/api/0.6/changeset/create", :method => :put }
36       )
37     end
38
39     ##
40     # test the query functionality of changesets
41     def test_index
42       private_user = create(:user, :data_public => false)
43       private_user_changeset = create(:changeset, :user => private_user)
44       private_user_closed_changeset = create(:changeset, :closed, :user => private_user)
45       user = create(:user)
46       changeset = create(:changeset, :user => user)
47       closed_changeset = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 1, 1, 0, 0, 0), :closed_at => Time.utc(2008, 1, 2, 0, 0, 0))
48       changeset2 = create(:changeset, :bbox => [5, 5, 15, 15])
49       changeset3 = create(:changeset, :bbox => [4.5, 4.5, 5, 5])
50
51       get api_changesets_path(:bbox => "-10,-10, 10, 10")
52       assert_response :success, "can't get changesets in bbox"
53       assert_changesets_in_order [changeset3, changeset2]
54
55       get api_changesets_path(:bbox => "4.5,4.5,4.6,4.6")
56       assert_response :success, "can't get changesets in bbox"
57       assert_changesets_in_order [changeset3]
58
59       # not found when looking for changesets of non-existing users
60       get api_changesets_path(:user => User.maximum(:id) + 1)
61       assert_response :not_found
62       assert_equal "text/plain", @response.media_type
63       get api_changesets_path(:display_name => " ")
64       assert_response :not_found
65       assert_equal "text/plain", @response.media_type
66
67       # can't get changesets of user 1 without authenticating
68       get api_changesets_path(:user => private_user.id)
69       assert_response :not_found, "shouldn't be able to get changesets by non-public user (ID)"
70       get api_changesets_path(:display_name => private_user.display_name)
71       assert_response :not_found, "shouldn't be able to get changesets by non-public user (name)"
72
73       # but this should work
74       auth_header = bearer_authorization_header private_user
75       get api_changesets_path(:user => private_user.id), :headers => auth_header
76       assert_response :success, "can't get changesets by user ID"
77       assert_changesets_in_order [private_user_changeset, private_user_closed_changeset]
78
79       get api_changesets_path(:display_name => private_user.display_name), :headers => auth_header
80       assert_response :success, "can't get changesets by user name"
81       assert_changesets_in_order [private_user_changeset, private_user_closed_changeset]
82
83       # test json endpoint
84       get api_changesets_path(:display_name => private_user.display_name), :headers => auth_header, :params => { :format => "json" }
85       assert_response :success, "can't get changesets by user name"
86
87       js = ActiveSupport::JSON.decode(@response.body)
88       assert_not_nil js
89
90       assert_equal Settings.api_version, js["version"]
91       assert_equal Settings.generator, js["generator"]
92       assert_equal 2, js["changesets"].count
93
94       # check that the correct error is given when we provide both UID and name
95       get api_changesets_path(:user => private_user.id,
96                               :display_name => private_user.display_name), :headers => auth_header
97       assert_response :bad_request, "should be a bad request to have both ID and name specified"
98
99       get api_changesets_path(:user => private_user.id, :open => true), :headers => auth_header
100       assert_response :success, "can't get changesets by user and open"
101       assert_changesets_in_order [private_user_changeset]
102
103       get api_changesets_path(:time => "2007-12-31"), :headers => auth_header
104       assert_response :success, "can't get changesets by time-since"
105       assert_changesets_in_order [changeset3, changeset2, changeset, private_user_changeset, private_user_closed_changeset, closed_changeset]
106
107       get api_changesets_path(:time => "2008-01-01T12:34Z"), :headers => auth_header
108       assert_response :success, "can't get changesets by time-since with hour"
109       assert_changesets_in_order [changeset3, changeset2, changeset, private_user_changeset, private_user_closed_changeset, closed_changeset]
110
111       get api_changesets_path(:time => "2007-12-31T23:59Z,2008-01-02T00:01Z"), :headers => auth_header
112       assert_response :success, "can't get changesets by time-range"
113       assert_changesets_in_order [closed_changeset]
114
115       get api_changesets_path(:open => "true"), :headers => auth_header
116       assert_response :success, "can't get changesets by open-ness"
117       assert_changesets_in_order [changeset3, changeset2, changeset, private_user_changeset]
118
119       get api_changesets_path(:closed => "true"), :headers => auth_header
120       assert_response :success, "can't get changesets by closed-ness"
121       assert_changesets_in_order [private_user_closed_changeset, closed_changeset]
122
123       get api_changesets_path(:closed => "true", :user => private_user.id), :headers => auth_header
124       assert_response :success, "can't get changesets by closed-ness and user"
125       assert_changesets_in_order [private_user_closed_changeset]
126
127       get api_changesets_path(:closed => "true", :user => user.id), :headers => auth_header
128       assert_response :success, "can't get changesets by closed-ness and user"
129       assert_changesets_in_order [closed_changeset]
130
131       get api_changesets_path(:changesets => "#{private_user_changeset.id},#{changeset.id},#{closed_changeset.id}"), :headers => auth_header
132       assert_response :success, "can't get changesets by id (as comma-separated string)"
133       assert_changesets_in_order [changeset, private_user_changeset, closed_changeset]
134
135       get api_changesets_path(:changesets => ""), :headers => auth_header
136       assert_response :bad_request, "should be a bad request since changesets is empty"
137     end
138
139     ##
140     # test the query functionality of changesets with the limit parameter
141     def test_index_limit
142       user = create(:user)
143       changeset1 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 1, 1, 0, 0, 0), :closed_at => Time.utc(2008, 1, 2, 0, 0, 0))
144       changeset2 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 2, 1, 0, 0, 0), :closed_at => Time.utc(2008, 2, 2, 0, 0, 0))
145       changeset3 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 3, 1, 0, 0, 0), :closed_at => Time.utc(2008, 3, 2, 0, 0, 0))
146       changeset4 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 4, 1, 0, 0, 0), :closed_at => Time.utc(2008, 4, 2, 0, 0, 0))
147       changeset5 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 5, 1, 0, 0, 0), :closed_at => Time.utc(2008, 5, 2, 0, 0, 0))
148
149       get api_changesets_path
150       assert_response :success
151       assert_changesets_in_order [changeset5, changeset4, changeset3, changeset2, changeset1]
152
153       get api_changesets_path(:limit => "3")
154       assert_response :success
155       assert_changesets_in_order [changeset5, changeset4, changeset3]
156
157       get api_changesets_path(:limit => "0")
158       assert_response :bad_request
159
160       get api_changesets_path(:limit => Settings.max_changeset_query_limit)
161       assert_response :success
162       assert_changesets_in_order [changeset5, changeset4, changeset3, changeset2, changeset1]
163
164       get api_changesets_path(:limit => Settings.max_changeset_query_limit + 1)
165       assert_response :bad_request
166     end
167
168     ##
169     # test the query functionality of sequential changesets with order and time parameters
170     def test_index_order
171       user = create(:user)
172       changeset1 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 1, 1, 0, 0, 0), :closed_at => Time.utc(2008, 1, 2, 0, 0, 0))
173       changeset2 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 2, 1, 0, 0, 0), :closed_at => Time.utc(2008, 2, 2, 0, 0, 0))
174       changeset3 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 3, 1, 0, 0, 0), :closed_at => Time.utc(2008, 3, 2, 0, 0, 0))
175       changeset4 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 4, 1, 0, 0, 0), :closed_at => Time.utc(2008, 4, 2, 0, 0, 0))
176       changeset5 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 5, 1, 0, 0, 0), :closed_at => Time.utc(2008, 5, 2, 0, 0, 0))
177       changeset6 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 6, 1, 0, 0, 0), :closed_at => Time.utc(2008, 6, 2, 0, 0, 0))
178
179       get api_changesets_path
180       assert_response :success
181       assert_changesets_in_order [changeset6, changeset5, changeset4, changeset3, changeset2, changeset1]
182
183       get api_changesets_path(:order => "oldest")
184       assert_response :success
185       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4, changeset5, changeset6]
186
187       [
188         # lower time bound at the opening time of a changeset
189         ["2008-02-01T00:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3, changeset2]],
190         # lower time bound in the middle of a changeset
191         ["2008-02-01T12:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3]],
192         # lower time bound at the closing time of a changeset
193         ["2008-02-02T00:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3]],
194         # lower time bound after the closing time of a changeset
195         ["2008-02-02T00:00:01Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3], [changeset5, changeset4, changeset3]],
196         # upper time bound in the middle of a changeset
197         ["2007-09-09T12:00:00Z", "2008-04-01T12:00:00Z", [changeset4, changeset3, changeset2, changeset1], [changeset4, changeset3, changeset2, changeset1]],
198         # empty range
199         ["2009-02-02T00:00:01Z", "2018-05-15T00:00:00Z", [], []]
200       ].each do |from, to, interval_changesets, point_changesets|
201         get api_changesets_path(:time => "#{from},#{to}")
202         assert_response :success
203         assert_changesets_in_order interval_changesets
204
205         get api_changesets_path(:from => from, :to => to)
206         assert_response :success
207         assert_changesets_in_order point_changesets
208
209         get api_changesets_path(:from => from, :to => to, :order => "oldest")
210         assert_response :success
211         assert_changesets_in_order point_changesets.reverse
212       end
213     end
214
215     ##
216     # test the query functionality of overlapping changesets with order and time parameters
217     def test_index_order_overlapping
218       user = create(:user)
219       changeset1 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2015, 6, 4, 17, 0, 0), :closed_at => Time.utc(2015, 6, 4, 17, 0, 0))
220       changeset2 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2015, 6, 4, 16, 0, 0), :closed_at => Time.utc(2015, 6, 4, 18, 0, 0))
221       changeset3 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2015, 6, 4, 14, 0, 0), :closed_at => Time.utc(2015, 6, 4, 20, 0, 0))
222       changeset4 = create(:changeset, :closed, :user => user, :created_at => Time.utc(2015, 6, 3, 23, 0, 0), :closed_at => Time.utc(2015, 6, 4, 23, 0, 0))
223       create(:changeset, :closed, :user => user, :created_at => Time.utc(2015, 6, 2, 23, 0, 0), :closed_at => Time.utc(2015, 6, 3, 23, 0, 0))
224
225       get api_changesets_path(:time => "2015-06-04T00:00:00Z")
226       assert_response :success
227       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4]
228
229       get api_changesets_path(:from => "2015-06-04T00:00:00Z")
230       assert_response :success
231       assert_changesets_in_order [changeset1, changeset2, changeset3]
232
233       get api_changesets_path(:from => "2015-06-04T00:00:00Z", :order => "oldest")
234       assert_response :success
235       assert_changesets_in_order [changeset3, changeset2, changeset1]
236
237       get api_changesets_path(:time => "2015-06-04T16:00:00Z,2015-06-04T17:30:00Z")
238       assert_response :success
239       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4]
240
241       get api_changesets_path(:from => "2015-06-04T16:00:00Z", :to => "2015-06-04T17:30:00Z")
242       assert_response :success
243       assert_changesets_in_order [changeset1, changeset2]
244
245       get api_changesets_path(:from => "2015-06-04T16:00:00Z", :to => "2015-06-04T17:30:00Z", :order => "oldest")
246       assert_response :success
247       assert_changesets_in_order [changeset2, changeset1]
248     end
249
250     ##
251     # check that errors are returned if garbage is inserted
252     # into query strings
253     def test_index_invalid
254       ["abracadabra!",
255        "1,2,3,F",
256        ";drop table users;"].each do |bbox|
257         get api_changesets_path(:bbox => bbox)
258         assert_response :bad_request, "'#{bbox}' isn't a bbox"
259       end
260
261       ["now()",
262        "00-00-00",
263        ";drop table users;",
264        ",",
265        "-,-"].each do |time|
266         get api_changesets_path(:time => time)
267         assert_response :bad_request, "'#{time}' isn't a valid time range"
268       end
269
270       ["me",
271        "foobar",
272        "-1",
273        "0"].each do |uid|
274         get api_changesets_path(:user => uid)
275         assert_response :bad_request, "'#{uid}' isn't a valid user ID"
276       end
277
278       get api_changesets_path(:order => "oldest", :time => "2008-01-01T00:00Z,2018-01-01T00:00Z")
279       assert_response :bad_request, "cannot use order=oldest with time"
280     end
281
282     # -----------------------
283     # Test simple changeset creation
284     # -----------------------
285
286     def test_create
287       auth_header = bearer_authorization_header create(:user, :data_public => false)
288       # Create the first user's changeset
289       xml = "<osm><changeset>" \
290             "<tag k='created_by' v='osm test suite checking changesets'/>" \
291             "</changeset></osm>"
292       post api_changesets_path, :params => xml, :headers => auth_header
293       assert_require_public_data
294
295       auth_header = bearer_authorization_header
296       # Create the first user's changeset
297       xml = "<osm><changeset>" \
298             "<tag k='created_by' v='osm test suite checking changesets'/>" \
299             "</changeset></osm>"
300       post api_changesets_path, :params => xml, :headers => auth_header
301
302       assert_response :success, "Creation of changeset did not return success status"
303       newid = @response.body.to_i
304
305       # check end time, should be an hour ahead of creation time
306       cs = Changeset.find(newid)
307       duration = cs.closed_at - cs.created_at
308       # the difference can either be a rational, or a floating point number
309       # of seconds, depending on the code path taken :-(
310       if duration.instance_of?(Rational)
311         assert_equal Rational(1, 24), duration, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
312       else
313         # must be number of seconds...
314         assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
315       end
316
317       # checks if uploader was subscribed
318       assert_equal 1, cs.subscribers.length
319     end
320
321     def test_create_invalid
322       auth_header = bearer_authorization_header create(:user, :data_public => false)
323       xml = "<osm><changeset></osm>"
324       post api_changesets_path, :params => xml, :headers => auth_header
325       assert_require_public_data
326
327       ## Try the public user
328       auth_header = bearer_authorization_header
329       xml = "<osm><changeset></osm>"
330       post api_changesets_path, :params => xml, :headers => auth_header
331       assert_response :bad_request, "creating a invalid changeset should fail"
332     end
333
334     def test_create_invalid_no_content
335       ## First check with no auth
336       post api_changesets_path
337       assert_response :unauthorized, "shouldn't be able to create a changeset with no auth"
338
339       ## Now try to with a non-public user
340       auth_header = bearer_authorization_header create(:user, :data_public => false)
341       post api_changesets_path, :headers => auth_header
342       assert_require_public_data
343
344       ## Try an inactive user
345       auth_header = bearer_authorization_header create(:user, :pending)
346       post api_changesets_path, :headers => auth_header
347       assert_inactive_user
348
349       ## Now try to use a normal user
350       auth_header = bearer_authorization_header
351       post api_changesets_path, :headers => auth_header
352       assert_response :bad_request, "creating a changeset with no content should fail"
353     end
354
355     def test_create_wrong_method
356       auth_header = bearer_authorization_header
357
358       put api_changesets_path, :headers => auth_header
359       assert_response :not_found
360       assert_template "rescues/routing_error"
361     end
362
363     def test_create_legacy_path
364       auth_header = bearer_authorization_header
365       xml = "<osm><changeset></changeset></osm>"
366
367       assert_difference "Changeset.count", 1 do
368         put "/api/0.6/changeset/create", :params => xml, :headers => auth_header
369       end
370
371       assert_response :success, "Creation of changeset did not return success status"
372       assert_equal Changeset.last.id, @response.body.to_i
373     end
374
375     ##
376     # check that the changeset can be shown and returns the correct
377     # document structure.
378     def test_show
379       changeset = create(:changeset)
380
381       get api_changeset_path(changeset)
382       assert_response :success, "cannot get first changeset"
383
384       assert_dom "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
385       assert_single_changeset changeset do
386         assert_dom "> discussion", 0
387       end
388
389       get api_changeset_path(changeset, :include_discussion => true)
390       assert_response :success, "cannot get first changeset with comments"
391
392       assert_dom "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
393       assert_single_changeset changeset do
394         assert_dom "> discussion", 1
395         assert_dom "> discussion > comment", 0
396       end
397     end
398
399     def test_show_comments
400       # all comments visible
401       changeset = create(:changeset, :closed)
402       comment1, comment2, comment3 = create_list(:changeset_comment, 3, :changeset_id => changeset.id)
403
404       get api_changeset_path(changeset, :include_discussion => true)
405       assert_response :success, "cannot get closed changeset with comments"
406
407       assert_dom "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
408         assert_single_changeset changeset do
409           assert_dom "> discussion", 1 do
410             assert_dom "> comment", 3 do |dom_comments|
411               assert_dom dom_comments[0], "> @id", comment1.id.to_s
412               assert_dom dom_comments[0], "> @visible", "true"
413               assert_dom dom_comments[1], "> @id", comment2.id.to_s
414               assert_dom dom_comments[1], "> @visible", "true"
415               assert_dom dom_comments[2], "> @id", comment3.id.to_s
416               assert_dom dom_comments[2], "> @visible", "true"
417             end
418           end
419         end
420       end
421
422       # one hidden comment not included because not asked for
423       comment2.update(:visible => false)
424       changeset.reload
425
426       get api_changeset_path(changeset, :include_discussion => true)
427       assert_response :success, "cannot get closed changeset with comments"
428
429       assert_dom "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
430       assert_single_changeset changeset do
431         assert_dom "> discussion", 1 do
432           assert_dom "> comment", 2 do |dom_comments|
433             assert_dom dom_comments[0], "> @id", comment1.id.to_s
434             assert_dom dom_comments[0], "> @visible", "true"
435             assert_dom dom_comments[1], "> @id", comment3.id.to_s
436             assert_dom dom_comments[1], "> @visible", "true"
437           end
438         end
439       end
440
441       # one hidden comment not included because no permissions
442       get api_changeset_path(changeset, :include_discussion => true, :show_hidden_comments => true)
443       assert_response :success, "cannot get closed changeset with comments"
444
445       assert_dom "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
446       assert_single_changeset changeset do
447         assert_dom "> discussion", 1 do
448           assert_dom "> comment", 2 do |dom_comments|
449             assert_dom dom_comments[0], "> @id", comment1.id.to_s
450             assert_dom dom_comments[0], "> @visible", "true"
451             # maybe will show an empty comment element with visible=false in the future
452             assert_dom dom_comments[1], "> @id", comment3.id.to_s
453             assert_dom dom_comments[1], "> @visible", "true"
454           end
455         end
456       end
457
458       # one hidden comment shown to moderators
459       moderator_user = create(:moderator_user)
460       auth_header = bearer_authorization_header moderator_user
461       get api_changeset_path(changeset, :include_discussion => true, :show_hidden_comments => true), :headers => auth_header
462       assert_response :success, "cannot get closed changeset with comments"
463
464       assert_dom "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
465       assert_single_changeset changeset do
466         assert_dom "> discussion", 1 do
467           assert_dom "> comment", 3 do |dom_comments|
468             assert_dom dom_comments[0], "> @id", comment1.id.to_s
469             assert_dom dom_comments[0], "> @visible", "true"
470             assert_dom dom_comments[1], "> @id", comment2.id.to_s
471             assert_dom dom_comments[1], "> @visible", "false"
472             assert_dom dom_comments[2], "> @id", comment3.id.to_s
473             assert_dom dom_comments[2], "> @visible", "true"
474           end
475         end
476       end
477     end
478
479     def test_show_tags
480       changeset = create(:changeset, :closed)
481       create(:changeset_tag, :changeset => changeset, :k => "created_by", :v => "JOSM/1.5 (18364)")
482       create(:changeset_tag, :changeset => changeset, :k => "comment", :v => "changeset comment")
483
484       get api_changeset_path(changeset)
485
486       assert_response :success
487       assert_dom "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
488       assert_single_changeset changeset do
489         assert_dom "> tag", 2
490         assert_dom "> tag[k='created_by'][v='JOSM/1.5 (18364)']", 1
491         assert_dom "> tag[k='comment'][v='changeset comment']", 1
492       end
493     end
494
495     def test_show_json
496       changeset = create(:changeset)
497
498       get api_changeset_path(changeset, :format => "json")
499       assert_response :success, "cannot get first changeset"
500
501       js = ActiveSupport::JSON.decode(@response.body)
502       assert_not_nil js
503
504       assert_equal Settings.api_version, js["version"]
505       assert_equal Settings.generator, js["generator"]
506       assert_single_changeset_json changeset, js
507       assert_nil js["changeset"]["tags"]
508       assert_nil js["changeset"]["comments"]
509       assert_equal changeset.user.id, js["changeset"]["uid"]
510       assert_equal changeset.user.display_name, js["changeset"]["user"]
511
512       get api_changeset_path(changeset, :format => "json", :include_discussion => true)
513       assert_response :success, "cannot get first changeset with comments"
514
515       js = ActiveSupport::JSON.decode(@response.body)
516       assert_not_nil js
517       assert_equal Settings.api_version, js["version"]
518       assert_equal Settings.generator, js["generator"]
519       assert_single_changeset_json changeset, js
520       assert_nil js["changeset"]["tags"]
521       assert_nil js["changeset"]["min_lat"]
522       assert_nil js["changeset"]["min_lon"]
523       assert_nil js["changeset"]["max_lat"]
524       assert_nil js["changeset"]["max_lon"]
525       assert_equal 0, js["changeset"]["comments"].count
526     end
527
528     def test_show_comments_json
529       # all comments visible
530       changeset = create(:changeset, :closed)
531       comment0, comment1, comment2 = create_list(:changeset_comment, 3, :changeset_id => changeset.id)
532
533       get api_changeset_path(changeset, :format => "json", :include_discussion => true)
534       assert_response :success, "cannot get closed changeset with comments"
535
536       js = ActiveSupport::JSON.decode(@response.body)
537       assert_not_nil js
538       assert_equal Settings.api_version, js["version"]
539       assert_equal Settings.generator, js["generator"]
540       assert_single_changeset_json changeset, js
541       assert_equal 3, js["changeset"]["comments"].count
542       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
543       assert js["changeset"]["comments"][0]["visible"]
544       assert_equal comment1.id, js["changeset"]["comments"][1]["id"]
545       assert js["changeset"]["comments"][1]["visible"]
546       assert_equal comment2.id, js["changeset"]["comments"][2]["id"]
547       assert js["changeset"]["comments"][2]["visible"]
548
549       # one hidden comment not included because not asked for
550       comment1.update(:visible => false)
551       changeset.reload
552
553       get api_changeset_path(changeset, :format => "json", :include_discussion => true)
554       assert_response :success, "cannot get closed changeset with comments"
555
556       js = ActiveSupport::JSON.decode(@response.body)
557       assert_not_nil js
558       assert_equal Settings.api_version, js["version"]
559       assert_equal Settings.generator, js["generator"]
560       assert_single_changeset_json changeset, js
561       assert_equal 2, js["changeset"]["comments"].count
562       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
563       assert js["changeset"]["comments"][0]["visible"]
564       assert_equal comment2.id, js["changeset"]["comments"][1]["id"]
565       assert js["changeset"]["comments"][1]["visible"]
566
567       # one hidden comment not included because no permissions
568       get api_changeset_path(changeset, :format => "json", :include_discussion => true, :show_hidden_comments => true)
569       assert_response :success, "cannot get closed changeset with comments"
570
571       js = ActiveSupport::JSON.decode(@response.body)
572       assert_not_nil js
573       assert_equal Settings.api_version, js["version"]
574       assert_equal Settings.generator, js["generator"]
575       assert_single_changeset_json changeset, js
576       assert_equal 2, js["changeset"]["comments"].count
577       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
578       assert js["changeset"]["comments"][0]["visible"]
579       # maybe will show an empty comment element with visible=false in the future
580       assert_equal comment2.id, js["changeset"]["comments"][1]["id"]
581       assert js["changeset"]["comments"][1]["visible"]
582
583       # one hidden comment shown to moderators
584       moderator_user = create(:moderator_user)
585       auth_header = bearer_authorization_header moderator_user
586       get api_changeset_path(changeset, :format => "json", :include_discussion => true, :show_hidden_comments => true), :headers => auth_header
587       assert_response :success, "cannot get closed changeset with comments"
588
589       js = ActiveSupport::JSON.decode(@response.body)
590       assert_not_nil js
591       assert_equal Settings.api_version, js["version"]
592       assert_equal Settings.generator, js["generator"]
593       assert_single_changeset_json changeset, js
594       assert_equal 3, js["changeset"]["comments"].count
595       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
596       assert js["changeset"]["comments"][0]["visible"]
597       assert_equal comment1.id, js["changeset"]["comments"][1]["id"]
598       assert_not js["changeset"]["comments"][1]["visible"]
599       assert_equal comment2.id, js["changeset"]["comments"][2]["id"]
600       assert js["changeset"]["comments"][2]["visible"]
601     end
602
603     def test_show_tags_json
604       changeset = create(:changeset, :closed)
605       create(:changeset_tag, :changeset => changeset, :k => "created_by", :v => "JOSM/1.5 (18364)")
606       create(:changeset_tag, :changeset => changeset, :k => "comment", :v => "changeset comment")
607
608       get api_changeset_path(changeset, :format => "json")
609
610       assert_response :success
611       js = ActiveSupport::JSON.decode(@response.body)
612       assert_not_nil js
613       assert_equal Settings.api_version, js["version"]
614       assert_equal Settings.generator, js["generator"]
615       assert_single_changeset_json changeset, js
616       assert_equal 2, js["changeset"]["tags"].count
617       assert_equal "JOSM/1.5 (18364)", js["changeset"]["tags"]["created_by"]
618       assert_equal "changeset comment", js["changeset"]["tags"]["comment"]
619     end
620
621     def test_show_bbox_json
622       changeset = create(:changeset, :bbox => [5, -5, 12, 15])
623
624       get api_changeset_path(changeset, :format => "json")
625       assert_response :success, "cannot get first changeset"
626
627       js = ActiveSupport::JSON.decode(@response.body)
628       assert_not_nil js
629       assert_equal(-5, js["changeset"]["min_lat"])
630       assert_equal 5, js["changeset"]["min_lon"]
631       assert_equal 15, js["changeset"]["max_lat"]
632       assert_equal 12, js["changeset"]["max_lon"]
633     end
634
635     ##
636     # check that a changeset that doesn't exist returns an appropriate message
637     def test_show_not_found
638       [0, -32, 233455644, "afg", "213"].each do |id|
639         get api_changeset_path(id)
640         assert_response :not_found, "should get a not found"
641       rescue ActionController::UrlGenerationError => e
642         assert_match(/No route matches/, e.to_s)
643       end
644     end
645
646     ##
647     # upload something which creates new objects using placeholders
648     def test_upload_create_valid
649       user = create(:user)
650       changeset = create(:changeset, :user => user)
651       node = create(:node)
652       way = create(:way_with_nodes, :nodes_count => 2)
653       relation = create(:relation)
654
655       auth_header = bearer_authorization_header user
656
657       # simple diff to create a node way and relation using placeholders
658       diff = <<~CHANGESET
659         <osmChange>
660          <create>
661           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
662            <tag k='foo' v='bar'/>
663            <tag k='baz' v='bat'/>
664           </node>
665           <way id='-1' changeset='#{changeset.id}'>
666            <nd ref='#{node.id}'/>
667           </way>
668          </create>
669          <create>
670           <relation id='-1' changeset='#{changeset.id}'>
671            <member type='way' role='some' ref='#{way.id}'/>
672            <member type='node' role='some' ref='#{node.id}'/>
673            <member type='relation' role='some' ref='#{relation.id}'/>
674           </relation>
675          </create>
676         </osmChange>
677       CHANGESET
678
679       # upload it
680       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
681       assert_response :success,
682                       "can't upload a simple valid creation to changeset: #{@response.body}"
683
684       # check the returned payload
685       new_node_id, new_way_id, new_rel_id = nil
686       assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
687         # inspect the response to find out what the new element IDs are
688         # check the old IDs are all present and negative one
689         # check the versions are present and equal one
690         assert_dom "> node", 1 do |(node_el)|
691           new_node_id = node_el["new_id"].to_i
692           assert_dom "> @old_id", "-1"
693           assert_dom "> @new_version", "1"
694         end
695         assert_dom "> way", 1 do |(way_el)|
696           new_way_id = way_el["new_id"].to_i
697           assert_dom "> @old_id", "-1"
698           assert_dom "> @new_version", "1"
699         end
700         assert_dom "> relation", 1 do |(rel_el)|
701           new_rel_id = rel_el["new_id"].to_i
702           assert_dom "> @old_id", "-1"
703           assert_dom "> @new_version", "1"
704         end
705       end
706
707       # check that the changes made it into the database
708       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
709       assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
710       assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
711     end
712
713     ##
714     # test a complex delete where we delete elements which rely on eachother
715     # in the same transaction.
716     def test_upload_delete
717       changeset = create(:changeset)
718       super_relation = create(:relation)
719       used_relation = create(:relation)
720       used_way = create(:way)
721       used_node = create(:node)
722       create(:relation_member, :relation => super_relation, :member => used_relation)
723       create(:relation_member, :relation => super_relation, :member => used_way)
724       create(:relation_member, :relation => super_relation, :member => used_node)
725
726       auth_header = bearer_authorization_header changeset.user
727
728       diff = XML::Document.new
729       diff.root = XML::Node.new "osmChange"
730       delete = XML::Node.new "delete"
731       diff.root << delete
732       delete << xml_node_for_relation(super_relation)
733       delete << xml_node_for_relation(used_relation)
734       delete << xml_node_for_way(used_way)
735       delete << xml_node_for_node(used_node)
736
737       # update the changeset to one that this user owns
738       %w[node way relation].each do |type|
739         delete.find("//osmChange/delete/#{type}").each do |n|
740           n["changeset"] = changeset.id.to_s
741         end
742       end
743
744       # upload it
745       post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
746       assert_response :success,
747                       "can't upload a deletion diff to changeset: #{@response.body}"
748
749       # check the response is well-formed
750       assert_select "diffResult>node", 1
751       assert_select "diffResult>way", 1
752       assert_select "diffResult>relation", 2
753
754       # check that everything was deleted
755       assert_not Node.find(used_node.id).visible
756       assert_not Way.find(used_way.id).visible
757       assert_not Relation.find(super_relation.id).visible
758       assert_not Relation.find(used_relation.id).visible
759     end
760
761     ##
762     # test uploading a delete with no lat/lon, as they are optional in
763     # the osmChange spec.
764     def test_upload_nolatlon_delete
765       node = create(:node)
766       changeset = create(:changeset)
767
768       auth_header = bearer_authorization_header changeset.user
769       diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
770
771       # upload it
772       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
773       assert_response :success,
774                       "can't upload a deletion diff to changeset: #{@response.body}"
775
776       # check the response is well-formed
777       assert_select "diffResult>node", 1
778
779       # check that everything was deleted
780       assert_not Node.find(node.id).visible
781     end
782
783     def test_repeated_changeset_create
784       3.times do
785         auth_header = bearer_authorization_header
786
787         # create a temporary changeset
788         xml = "<osm><changeset>" \
789               "<tag k='created_by' v='osm test suite checking changesets'/>" \
790               "</changeset></osm>"
791         assert_difference "Changeset.count", 1 do
792           post api_changesets_path, :params => xml, :headers => auth_header
793         end
794         assert_response :success
795       end
796     end
797
798     def test_upload_large_changeset
799       user = create(:user)
800       auth_header = bearer_authorization_header user
801
802       # create an old changeset to ensure we have the maximum rate limit
803       create(:changeset, :user => user, :created_at => Time.now.utc - 28.days)
804
805       # create a changeset
806       post api_changesets_path, :params => "<osm><changeset/></osm>", :headers => auth_header
807       assert_response :success, "Should be able to create a changeset: #{@response.body}"
808       changeset_id = @response.body.to_i
809
810       # upload some widely-spaced nodes, spiralling positive and negative
811       diff = <<~CHANGESET
812         <osmChange>
813          <create>
814           <node id='-1' lon='-20' lat='-10' changeset='#{changeset_id}'/>
815           <node id='-10' lon='20'  lat='10' changeset='#{changeset_id}'/>
816           <node id='-2' lon='-40' lat='-20' changeset='#{changeset_id}'/>
817           <node id='-11' lon='40'  lat='20' changeset='#{changeset_id}'/>
818           <node id='-3' lon='-60' lat='-30' changeset='#{changeset_id}'/>
819           <node id='-12' lon='60'  lat='30' changeset='#{changeset_id}'/>
820           <node id='-4' lon='-80' lat='-40' changeset='#{changeset_id}'/>
821           <node id='-13' lon='80'  lat='40' changeset='#{changeset_id}'/>
822           <node id='-5' lon='-100' lat='-50' changeset='#{changeset_id}'/>
823           <node id='-14' lon='100'  lat='50' changeset='#{changeset_id}'/>
824           <node id='-6' lon='-120' lat='-60' changeset='#{changeset_id}'/>
825           <node id='-15' lon='120'  lat='60' changeset='#{changeset_id}'/>
826           <node id='-7' lon='-140' lat='-70' changeset='#{changeset_id}'/>
827           <node id='-16' lon='140'  lat='70' changeset='#{changeset_id}'/>
828           <node id='-8' lon='-160' lat='-80' changeset='#{changeset_id}'/>
829           <node id='-17' lon='160'  lat='80' changeset='#{changeset_id}'/>
830           <node id='-9' lon='-179.9' lat='-89.9' changeset='#{changeset_id}'/>
831           <node id='-18' lon='179.9'  lat='89.9' changeset='#{changeset_id}'/>
832          </create>
833         </osmChange>
834       CHANGESET
835
836       # upload it, which used to cause an error like "PGError: ERROR:
837       # integer out of range" (bug #2152). but shouldn't any more.
838       post api_changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
839       assert_response :success,
840                       "can't upload a spatially-large diff to changeset: #{@response.body}"
841
842       # check that the changeset bbox is within bounds
843       cs = Changeset.find(changeset_id)
844       assert_operator cs.min_lon, :>=, -180 * GeoRecord::SCALE, "Minimum longitude (#{cs.min_lon / GeoRecord::SCALE}) should be >= -180 to be valid."
845       assert_operator cs.max_lon, :<=, 180 * GeoRecord::SCALE, "Maximum longitude (#{cs.max_lon / GeoRecord::SCALE}) should be <= 180 to be valid."
846       assert_operator cs.min_lat, :>=, -90 * GeoRecord::SCALE, "Minimum latitude (#{cs.min_lat / GeoRecord::SCALE}) should be >= -90 to be valid."
847       assert_operator cs.max_lat, :<=, 90 * GeoRecord::SCALE, "Maximum latitude (#{cs.max_lat / GeoRecord::SCALE}) should be <= 90 to be valid."
848     end
849
850     ##
851     # test that deleting stuff in a transaction doesn't bypass the checks
852     # to ensure that used elements are not deleted.
853     def test_upload_delete_invalid
854       changeset = create(:changeset)
855       relation = create(:relation)
856       other_relation = create(:relation)
857       used_way = create(:way)
858       used_node = create(:node)
859       create(:relation_member, :relation => relation, :member => used_way)
860       create(:relation_member, :relation => relation, :member => used_node)
861
862       auth_header = bearer_authorization_header changeset.user
863
864       diff = XML::Document.new
865       diff.root = XML::Node.new "osmChange"
866       delete = XML::Node.new "delete"
867       diff.root << delete
868       delete << xml_node_for_relation(other_relation)
869       delete << xml_node_for_way(used_way)
870       delete << xml_node_for_node(used_node)
871
872       # update the changeset to one that this user owns
873       %w[node way relation].each do |type|
874         delete.find("//osmChange/delete/#{type}").each do |n|
875           n["changeset"] = changeset.id.to_s
876         end
877       end
878
879       # upload it
880       post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
881       assert_response :precondition_failed,
882                       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
883       assert_equal "Precondition failed: Way #{used_way.id} is still used by relations #{relation.id}.", @response.body
884
885       # check that nothing was, in fact, deleted
886       assert Node.find(used_node.id).visible
887       assert Way.find(used_way.id).visible
888       assert Relation.find(relation.id).visible
889       assert Relation.find(other_relation.id).visible
890     end
891
892     ##
893     # test that a conditional delete of an in use object works.
894     def test_upload_delete_if_unused
895       changeset = create(:changeset)
896       super_relation = create(:relation)
897       used_relation = create(:relation)
898       used_way = create(:way)
899       used_node = create(:node)
900       create(:relation_member, :relation => super_relation, :member => used_relation)
901       create(:relation_member, :relation => super_relation, :member => used_way)
902       create(:relation_member, :relation => super_relation, :member => used_node)
903
904       auth_header = bearer_authorization_header changeset.user
905
906       diff = XML::Document.new
907       diff.root = XML::Node.new "osmChange"
908       delete = XML::Node.new "delete"
909       diff.root << delete
910       delete["if-unused"] = ""
911       delete << xml_node_for_relation(used_relation)
912       delete << xml_node_for_way(used_way)
913       delete << xml_node_for_node(used_node)
914
915       # update the changeset to one that this user owns
916       %w[node way relation].each do |type|
917         delete.find("//osmChange/delete/#{type}").each do |n|
918           n["changeset"] = changeset.id.to_s
919         end
920       end
921
922       # upload it
923       post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
924       assert_response :success,
925                       "can't do a conditional delete of in use objects: #{@response.body}"
926
927       # check the returned payload
928       assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
929         # check the old IDs are all present and what we expect
930         # check the new IDs are all present and unchanged
931         # check the new versions are all present and unchanged
932         assert_dom "> node", 1 do
933           assert_dom "> @old_id", used_node.id.to_s
934           assert_dom "> @new_id", used_node.id.to_s
935           assert_dom "> @new_version", used_node.version.to_s
936         end
937         assert_dom "> way", 1 do
938           assert_dom "> @old_id", used_way.id.to_s
939           assert_dom "> @new_id", used_way.id.to_s
940           assert_dom "> @new_version", used_way.version.to_s
941         end
942         assert_dom "> relation", 1 do
943           assert_dom "> @old_id", used_relation.id.to_s
944           assert_dom "> @new_id", used_relation.id.to_s
945           assert_dom "> @new_version", used_relation.version.to_s
946         end
947       end
948
949       # check that nothing was, in fact, deleted
950       assert Node.find(used_node.id).visible
951       assert Way.find(used_way.id).visible
952       assert Relation.find(used_relation.id).visible
953     end
954
955     ##
956     # upload an element with a really long tag value
957     def test_upload_invalid_too_long_tag
958       changeset = create(:changeset)
959
960       auth_header = bearer_authorization_header changeset.user
961
962       # simple diff to create a node way and relation using placeholders
963       diff = <<~CHANGESET
964         <osmChange>
965          <create>
966           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
967            <tag k='foo' v='#{'x' * 256}'/>
968           </node>
969          </create>
970         </osmChange>
971       CHANGESET
972
973       # upload it
974       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
975       assert_response :bad_request,
976                       "shouldn't be able to upload too long a tag to changeset: #{@response.body}"
977     end
978
979     ##
980     # upload something which creates new objects and inserts them into
981     # existing containers using placeholders.
982     def test_upload_complex
983       way = create(:way)
984       node = create(:node)
985       relation = create(:relation)
986       create(:way_node, :way => way, :node => node)
987
988       changeset = create(:changeset)
989
990       auth_header = bearer_authorization_header changeset.user
991
992       # simple diff to create a node way and relation using placeholders
993       diff = <<~CHANGESET
994         <osmChange>
995          <create>
996           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
997            <tag k='foo' v='bar'/>
998            <tag k='baz' v='bat'/>
999           </node>
1000          </create>
1001          <modify>
1002           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
1003            <nd ref='-1'/>
1004            <nd ref='#{node.id}'/>
1005           </way>
1006           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
1007            <member type='way' role='some' ref='#{way.id}'/>
1008            <member type='node' role='some' ref='-1'/>
1009            <member type='relation' role='some' ref='#{relation.id}'/>
1010           </relation>
1011          </modify>
1012         </osmChange>
1013       CHANGESET
1014
1015       # upload it
1016       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1017       assert_response :success,
1018                       "can't upload a complex diff to changeset: #{@response.body}"
1019
1020       # check the returned payload
1021       new_node_id = nil
1022       assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
1023         assert_dom "> node", 1 do |(node_el)|
1024           new_node_id = node_el["new_id"].to_i
1025         end
1026         assert_dom "> way", 1
1027         assert_dom "> relation", 1
1028       end
1029
1030       # check that the changes made it into the database
1031       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
1032       assert_equal [new_node_id, node.id], Way.find(way.id).nds, "way nodes should match"
1033       Relation.find(relation.id).members.each do |type, id, _role|
1034         assert_equal new_node_id, id, "relation should contain new node" if type == "node"
1035       end
1036     end
1037
1038     ##
1039     # create a diff which references several changesets, which should cause
1040     # a rollback and none of the diff gets committed
1041     def test_upload_invalid_changesets
1042       changeset = create(:changeset)
1043       other_changeset = create(:changeset, :user => changeset.user)
1044       node = create(:node)
1045       way = create(:way)
1046       relation = create(:relation)
1047       other_relation = create(:relation)
1048
1049       auth_header = bearer_authorization_header changeset.user
1050
1051       # simple diff to create a node way and relation using placeholders
1052       diff = <<~CHANGESET
1053         <osmChange>
1054          <modify>
1055           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1056           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
1057            <nd ref='#{node.id}'/>
1058           </way>
1059          </modify>
1060          <modify>
1061           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
1062            <member type='way' role='some' ref='#{way.id}'/>
1063            <member type='node' role='some' ref='#{node.id}'/>
1064            <member type='relation' role='some' ref='#{other_relation.id}'/>
1065           </relation>
1066          </modify>
1067          <create>
1068           <node id='-1' lon='0' lat='0' changeset='#{other_changeset.id}'>
1069            <tag k='foo' v='bar'/>
1070            <tag k='baz' v='bat'/>
1071           </node>
1072          </create>
1073         </osmChange>
1074       CHANGESET
1075
1076       # upload it
1077       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1078       assert_response :conflict,
1079                       "uploading a diff with multiple changesets should have failed"
1080
1081       # check that objects are unmodified
1082       assert_nodes_are_equal(node, Node.find(node.id))
1083       assert_ways_are_equal(way, Way.find(way.id))
1084       assert_relations_are_equal(relation, Relation.find(relation.id))
1085     end
1086
1087     ##
1088     # upload multiple versions of the same element in the same diff.
1089     def test_upload_multiple_valid
1090       node = create(:node)
1091       changeset = create(:changeset)
1092       auth_header = bearer_authorization_header changeset.user
1093
1094       # change the location of a node multiple times, each time referencing
1095       # the last version. doesn't this depend on version numbers being
1096       # sequential?
1097       diff = <<~CHANGESET
1098         <osmChange>
1099          <modify>
1100           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset.id}' version='1'/>
1101           <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset.id}' version='2'/>
1102           <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset.id}' version='3'/>
1103           <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset.id}' version='4'/>
1104           <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset.id}' version='5'/>
1105           <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset.id}' version='6'/>
1106           <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset.id}' version='7'/>
1107           <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset.id}' version='8'/>
1108          </modify>
1109         </osmChange>
1110       CHANGESET
1111
1112       # upload it
1113       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1114       assert_response :success,
1115                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1116
1117       # check the response is well-formed. its counter-intuitive, but the
1118       # API will return multiple elements with the same ID and different
1119       # version numbers for each change we made.
1120       assert_select "diffResult>node", 8
1121     end
1122
1123     ##
1124     # upload multiple versions of the same element in the same diff, but
1125     # keep the version numbers the same.
1126     def test_upload_multiple_duplicate
1127       node = create(:node)
1128       changeset = create(:changeset)
1129
1130       auth_header = bearer_authorization_header changeset.user
1131
1132       diff = <<~CHANGESET
1133         <osmChange>
1134          <modify>
1135           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1136           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1137          </modify>
1138         </osmChange>
1139       CHANGESET
1140
1141       # upload it
1142       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1143       assert_response :conflict,
1144                       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
1145     end
1146
1147     ##
1148     # try to upload some elements without specifying the version
1149     def test_upload_missing_version
1150       changeset = create(:changeset)
1151
1152       auth_header = bearer_authorization_header changeset.user
1153
1154       diff = <<~CHANGESET
1155         <osmChange>
1156          <modify>
1157          <node id='1' lon='1' lat='1' changeset='#{changeset.id}'/>
1158          </modify>
1159         </osmChange>
1160       CHANGESET
1161
1162       # upload it
1163       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1164       assert_response :bad_request,
1165                       "shouldn't be able to upload an element without version: #{@response.body}"
1166     end
1167
1168     ##
1169     # try to upload with commands other than create, modify, or delete
1170     def test_action_upload_invalid
1171       changeset = create(:changeset)
1172
1173       auth_header = bearer_authorization_header changeset.user
1174
1175       diff = <<~CHANGESET
1176         <osmChange>
1177           <ping>
1178            <node id='1' lon='1' lat='1' changeset='#{changeset.id}' />
1179           </ping>
1180         </osmChange>
1181       CHANGESET
1182       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1183       assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
1184       assert_equal("Unknown action ping, choices are create, modify, delete", @response.body)
1185     end
1186
1187     ##
1188     # upload a valid changeset which has a mixture of whitespace
1189     # to check a bug reported by ivansanchez (#1565).
1190     def test_upload_whitespace_valid
1191       changeset = create(:changeset)
1192       node = create(:node)
1193       way = create(:way_with_nodes, :nodes_count => 2)
1194       relation = create(:relation)
1195       other_relation = create(:relation)
1196       create(:relation_tag, :relation => relation)
1197
1198       auth_header = bearer_authorization_header changeset.user
1199
1200       diff = <<~CHANGESET
1201         <osmChange>
1202          <modify><node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}'
1203           version='1'></node>
1204           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='2'><tag k='k' v='v'/></node></modify>
1205          <modify>
1206          <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'><member
1207            type='way' role='some' ref='#{way.id}'/><member
1208             type='node' role='some' ref='#{node.id}'/>
1209            <member type='relation' role='some' ref='#{other_relation.id}'/>
1210           </relation>
1211          </modify></osmChange>
1212       CHANGESET
1213
1214       # upload it
1215       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1216       assert_response :success,
1217                       "can't upload a valid diff with whitespace variations to changeset: #{@response.body}"
1218
1219       # check the response is well-formed
1220       assert_select "diffResult>node", 2
1221       assert_select "diffResult>relation", 1
1222
1223       # check that the changes made it into the database
1224       assert_equal 1, Node.find(node.id).tags.size, "node #{node.id} should now have one tag"
1225       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
1226     end
1227
1228     ##
1229     # test that a placeholder can be reused within the same upload.
1230     def test_upload_reuse_placeholder_valid
1231       changeset = create(:changeset)
1232
1233       auth_header = bearer_authorization_header changeset.user
1234
1235       diff = <<~CHANGESET
1236         <osmChange>
1237          <create>
1238           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1239            <tag k="foo" v="bar"/>
1240           </node>
1241          </create>
1242          <modify>
1243           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1244          </modify>
1245          <delete>
1246           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
1247          </delete>
1248         </osmChange>
1249       CHANGESET
1250
1251       # upload it
1252       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1253       assert_response :success,
1254                       "can't upload a valid diff with re-used placeholders to changeset: #{@response.body}"
1255
1256       # check the response is well-formed
1257       assert_select "diffResult>node", 3
1258       assert_select "diffResult>node[old_id='-1']", 3
1259     end
1260
1261     ##
1262     # test what happens if a diff upload re-uses placeholder IDs in an
1263     # illegal way.
1264     def test_upload_placeholder_invalid
1265       changeset = create(:changeset)
1266
1267       auth_header = bearer_authorization_header changeset.user
1268
1269       diff = <<~CHANGESET
1270         <osmChange>
1271          <create>
1272           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1273           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1274           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
1275          </create>
1276         </osmChange>
1277       CHANGESET
1278
1279       # upload it
1280       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1281       assert_response :bad_request,
1282                       "shouldn't be able to re-use placeholder IDs"
1283
1284       # placeholder_ids must be unique across all action blocks
1285       diff = <<~CHANGESET
1286         <osmChange>
1287          <create>
1288           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1289          </create>
1290          <create>
1291           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1292          </create>
1293         </osmChange>
1294       CHANGESET
1295
1296       # upload it
1297       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1298       assert_response :bad_request,
1299                       "shouldn't be able to re-use placeholder IDs"
1300     end
1301
1302     def test_upload_process_order
1303       changeset = create(:changeset)
1304
1305       auth_header = bearer_authorization_header changeset.user
1306
1307       diff = <<~CHANGESET
1308         <osmChange>
1309         <create>
1310           <node id="-1" lat="1" lon="2" changeset="#{changeset.id}"/>
1311           <way id="-1" changeset="#{changeset.id}">
1312               <nd ref="-1"/>
1313               <nd ref="-2"/>
1314           </way>
1315           <node id="-2" lat="1" lon="2" changeset="#{changeset.id}"/>
1316         </create>
1317         </osmChange>
1318       CHANGESET
1319
1320       # upload it
1321       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1322       assert_response :bad_request,
1323                       "shouldn't refer elements behind it"
1324     end
1325
1326     def test_upload_duplicate_delete
1327       changeset = create(:changeset)
1328
1329       auth_header = bearer_authorization_header changeset.user
1330
1331       diff = <<~CHANGESET
1332         <osmChange>
1333           <create>
1334             <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1335           </create>
1336           <delete>
1337             <node id="-1" version="1" changeset="#{changeset.id}" />
1338             <node id="-1" version="1" changeset="#{changeset.id}" />
1339           </delete>
1340         </osmChange>
1341       CHANGESET
1342
1343       # upload it
1344       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1345       assert_response :gone,
1346                       "transaction should be cancelled by second deletion"
1347
1348       diff = <<~CHANGESET
1349         <osmChange>
1350           <create>
1351             <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1352           </create>
1353           <delete if-unused="true">
1354             <node id="-1" version="1" changeset="#{changeset.id}" />
1355             <node id="-1" version="1" changeset="#{changeset.id}" />
1356           </delete>
1357         </osmChange>
1358       CHANGESET
1359
1360       # upload it
1361       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1362
1363       assert_select "diffResult>node", 3
1364       assert_select "diffResult>node[old_id='-1']", 3
1365       assert_select "diffResult>node[new_version='1']", 1
1366       assert_select "diffResult>node[new_version='2']", 1
1367     end
1368
1369     ##
1370     # test that uploading a way referencing invalid placeholders gives a
1371     # proper error, not a 500.
1372     def test_upload_placeholder_invalid_way
1373       changeset = create(:changeset)
1374       way = create(:way)
1375
1376       auth_header = bearer_authorization_header changeset.user
1377
1378       diff = <<~CHANGESET
1379         <osmChange>
1380          <create>
1381           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1382           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1383           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1384           <way id="-1" changeset="#{changeset.id}" version="1">
1385            <nd ref="-1"/>
1386            <nd ref="-2"/>
1387            <nd ref="-3"/>
1388            <nd ref="-4"/>
1389           </way>
1390          </create>
1391         </osmChange>
1392       CHANGESET
1393
1394       # upload it
1395       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1396       assert_response :bad_request,
1397                       "shouldn't be able to use invalid placeholder IDs"
1398       assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
1399
1400       # the same again, but this time use an existing way
1401       diff = <<~CHANGESET
1402         <osmChange>
1403          <create>
1404           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1405           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1406           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1407           <way id="#{way.id}" changeset="#{changeset.id}" version="1">
1408            <nd ref="-1"/>
1409            <nd ref="-2"/>
1410            <nd ref="-3"/>
1411            <nd ref="-4"/>
1412           </way>
1413          </create>
1414         </osmChange>
1415       CHANGESET
1416
1417       # upload it
1418       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1419       assert_response :bad_request,
1420                       "shouldn't be able to use invalid placeholder IDs"
1421       assert_equal "Placeholder node not found for reference -4 in way #{way.id}", @response.body
1422     end
1423
1424     ##
1425     # test that uploading a relation referencing invalid placeholders gives a
1426     # proper error, not a 500.
1427     def test_upload_placeholder_invalid_relation
1428       changeset = create(:changeset)
1429       relation = create(:relation)
1430
1431       auth_header = bearer_authorization_header changeset.user
1432
1433       diff = <<~CHANGESET
1434         <osmChange>
1435          <create>
1436           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1437           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1438           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1439           <relation id="-1" changeset="#{changeset.id}" version="1">
1440            <member type="node" role="foo" ref="-1"/>
1441            <member type="node" role="foo" ref="-2"/>
1442            <member type="node" role="foo" ref="-3"/>
1443            <member type="node" role="foo" ref="-4"/>
1444           </relation>
1445          </create>
1446         </osmChange>
1447       CHANGESET
1448
1449       # upload it
1450       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1451       assert_response :bad_request,
1452                       "shouldn't be able to use invalid placeholder IDs"
1453       assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
1454
1455       # the same again, but this time use an existing relation
1456       diff = <<~CHANGESET
1457         <osmChange>
1458          <create>
1459           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1460           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1461           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1462           <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
1463            <member type="node" role="foo" ref="-1"/>
1464            <member type="node" role="foo" ref="-2"/>
1465            <member type="node" role="foo" ref="-3"/>
1466            <member type="way" role="bar" ref="-1"/>
1467           </relation>
1468          </create>
1469         </osmChange>
1470       CHANGESET
1471
1472       # upload it
1473       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1474       assert_response :bad_request,
1475                       "shouldn't be able to use invalid placeholder IDs"
1476       assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
1477     end
1478
1479     ##
1480     # test what happens if a diff is uploaded containing only a node
1481     # move.
1482     def test_upload_node_move
1483       auth_header = bearer_authorization_header
1484
1485       xml = "<osm><changeset>" \
1486             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1487             "</changeset></osm>"
1488       post api_changesets_path, :params => xml, :headers => auth_header
1489       assert_response :success
1490       changeset_id = @response.body.to_i
1491
1492       old_node = create(:node, :lat => 1, :lon => 1)
1493
1494       diff = XML::Document.new
1495       diff.root = XML::Node.new "osmChange"
1496       modify = XML::Node.new "modify"
1497       xml_old_node = xml_node_for_node(old_node)
1498       xml_old_node["lat"] = 2.0.to_s
1499       xml_old_node["lon"] = 2.0.to_s
1500       xml_old_node["changeset"] = changeset_id.to_s
1501       modify << xml_old_node
1502       diff.root << modify
1503
1504       # upload it
1505       post api_changeset_upload_path(changeset_id), :params => diff.to_s, :headers => auth_header
1506       assert_response :success,
1507                       "diff should have uploaded OK"
1508
1509       # check the bbox
1510       changeset = Changeset.find(changeset_id)
1511       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1512       assert_equal 2 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 2 degrees"
1513       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1514       assert_equal 2 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 2 degrees"
1515     end
1516
1517     ##
1518     # test what happens if a diff is uploaded adding a node to a way.
1519     def test_upload_way_extend
1520       auth_header = bearer_authorization_header
1521
1522       xml = "<osm><changeset>" \
1523             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1524             "</changeset></osm>"
1525       post api_changesets_path, :params => xml, :headers => auth_header
1526       assert_response :success
1527       changeset_id = @response.body.to_i
1528
1529       old_way = create(:way)
1530       create(:way_node, :way => old_way, :node => create(:node, :lat => 0.1, :lon => 0.1))
1531
1532       diff = XML::Document.new
1533       diff.root = XML::Node.new "osmChange"
1534       modify = XML::Node.new "modify"
1535       xml_old_way = xml_node_for_way(old_way)
1536       nd_ref = XML::Node.new "nd"
1537       nd_ref["ref"] = create(:node, :lat => 0.3, :lon => 0.3).id.to_s
1538       xml_old_way << nd_ref
1539       xml_old_way["changeset"] = changeset_id.to_s
1540       modify << xml_old_way
1541       diff.root << modify
1542
1543       # upload it
1544       post api_changeset_upload_path(changeset_id), :params => diff.to_s, :headers => auth_header
1545       assert_response :success,
1546                       "diff should have uploaded OK"
1547
1548       # check the bbox
1549       changeset = Changeset.find(changeset_id)
1550       assert_equal 0.1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 0.1 degree"
1551       assert_equal 0.3 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 0.3 degrees"
1552       assert_equal 0.1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 0.1 degree"
1553       assert_equal 0.3 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 0.3 degrees"
1554     end
1555
1556     ##
1557     # test for more issues in #1568
1558     def test_upload_empty_invalid
1559       changeset = create(:changeset)
1560
1561       auth_header = bearer_authorization_header changeset.user
1562
1563       ["<osmChange/>",
1564        "<osmChange></osmChange>",
1565        "<osmChange><modify/></osmChange>",
1566        "<osmChange><modify></modify></osmChange>"].each do |diff|
1567         # upload it
1568         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1569         assert_response(:success, "should be able to upload " \
1570                                   "empty changeset: " + diff)
1571       end
1572     end
1573
1574     ##
1575     # test that the X-Error-Format header works to request XML errors
1576     def test_upload_xml_errors
1577       changeset = create(:changeset)
1578       node = create(:node)
1579       create(:relation_member, :member => node)
1580
1581       auth_header = bearer_authorization_header changeset.user
1582
1583       # try and delete a node that is in use
1584       diff = XML::Document.new
1585       diff.root = XML::Node.new "osmChange"
1586       delete = XML::Node.new "delete"
1587       diff.root << delete
1588       delete << xml_node_for_node(node)
1589
1590       # upload it
1591       error_header = error_format_header "xml"
1592       post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header.merge(error_header)
1593       assert_response :success,
1594                       "failed to return error in XML format"
1595
1596       # check the returned payload
1597       assert_select "osmError[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
1598       assert_select "osmError>status", 1
1599       assert_select "osmError>message", 1
1600     end
1601
1602     def test_upload_not_found
1603       changeset = create(:changeset)
1604
1605       auth_header = bearer_authorization_header changeset.user
1606
1607       # modify node
1608       diff = <<~CHANGESET
1609         <osmChange>
1610         <modify>
1611           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1612         </modify>
1613         </osmChange>
1614       CHANGESET
1615
1616       # upload it
1617       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1618       assert_response :not_found, "Node should not be found"
1619
1620       # modify way
1621       diff = <<~CHANGESET
1622         <osmChange>
1623         <modify>
1624           <way id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1625         </modify>
1626         </osmChange>
1627       CHANGESET
1628
1629       # upload it
1630       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1631       assert_response :not_found, "Way should not be found"
1632
1633       # modify relation
1634       diff = <<~CHANGESET
1635         <osmChange>
1636         <modify>
1637           <relation id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1638         </modify>
1639         </osmChange>
1640       CHANGESET
1641
1642       # upload it
1643       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1644       assert_response :not_found, "Relation should not be found"
1645
1646       # delete node
1647       diff = <<~CHANGESET
1648         <osmChange>
1649         <delete>
1650           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1651         </delete>
1652         </osmChange>
1653       CHANGESET
1654
1655       # upload it
1656       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1657       assert_response :not_found, "Node should not be deleted"
1658
1659       # delete way
1660       diff = <<~CHANGESET
1661         <osmChange>
1662         <delete>
1663           <way id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1664         </delete>
1665         </osmChange>
1666       CHANGESET
1667
1668       # upload it
1669       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1670       assert_response :not_found, "Way should not be deleted"
1671
1672       # delete relation
1673       diff = <<~CHANGESET
1674         <osmChange>
1675         <delete>
1676           <relation id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1677         </delete>
1678         </osmChange>
1679       CHANGESET
1680
1681       # upload it
1682       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1683       assert_response :not_found, "Relation should not be deleted"
1684     end
1685
1686     def test_upload_relation_placeholder_not_fix
1687       changeset = create(:changeset)
1688
1689       auth_header = bearer_authorization_header changeset.user
1690
1691       # modify node
1692       diff = <<~CHANGESET
1693         <osmChange version='0.6'>
1694           <create>
1695             <relation id='-2' version='0' changeset='#{changeset.id}'>
1696               <member type='relation' role='' ref='-4' />
1697               <tag k='type' v='route' />
1698               <tag k='name' v='AtoB' />
1699             </relation>
1700             <relation id='-3' version='0' changeset='#{changeset.id}'>
1701               <tag k='type' v='route' />
1702               <tag k='name' v='BtoA' />
1703             </relation>
1704             <relation id='-4' version='0' changeset='#{changeset.id}'>
1705               <member type='relation' role='' ref='-2' />
1706               <member type='relation' role='' ref='-3' />
1707               <tag k='type' v='route_master' />
1708               <tag k='name' v='master' />
1709             </relation>
1710           </create>
1711         </osmChange>
1712       CHANGESET
1713
1714       # upload it
1715       post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1716       assert_response :bad_request, "shouldn't be able to use reference -4 in relation -2: #{@response.body}"
1717       assert_equal "Placeholder Relation not found for reference -4 in relation -2.", @response.body
1718     end
1719
1720     def test_upload_multiple_delete_block
1721       changeset = create(:changeset)
1722
1723       auth_header = bearer_authorization_header changeset.user
1724
1725       node = create(:node)
1726       way = create(:way)
1727       create(:way_node, :way => way, :node => node)
1728       alone_node = create(:node)
1729
1730       # modify node
1731       diff = <<~CHANGESET
1732         <osmChange version='0.6'>
1733           <delete version="0.6">
1734             <node id="#{node.id}" version="#{node.version}" changeset="#{changeset.id}"/>
1735           </delete>
1736           <delete version="0.6" if-unused="true">
1737             <node id="#{alone_node.id}" version="#{alone_node.version}" changeset="#{changeset.id}"/>
1738           </delete>
1739         </osmChange>
1740       CHANGESET
1741
1742       # upload it
1743       post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1744       assert_response :precondition_failed,
1745                       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
1746       assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way.id}.", @response.body
1747     end
1748
1749     ##
1750     # test initial rate limit
1751     def test_upload_initial_rate_limit
1752       # create a user
1753       user = create(:user)
1754
1755       # create some objects to use
1756       node = create(:node)
1757       way = create(:way_with_nodes, :nodes_count => 2)
1758       relation = create(:relation)
1759
1760       # create a changeset that puts us near the initial rate limit
1761       changeset = create(:changeset, :user => user,
1762                                      :created_at => Time.now.utc - 5.minutes,
1763                                      :num_changes => Settings.initial_changes_per_hour - 2)
1764
1765       # create authentication header
1766       auth_header = bearer_authorization_header user
1767
1768       # simple diff to create a node way and relation using placeholders
1769       diff = <<~CHANGESET
1770         <osmChange>
1771          <create>
1772           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1773            <tag k='foo' v='bar'/>
1774            <tag k='baz' v='bat'/>
1775           </node>
1776           <way id='-1' changeset='#{changeset.id}'>
1777            <nd ref='#{node.id}'/>
1778           </way>
1779          </create>
1780          <create>
1781           <relation id='-1' changeset='#{changeset.id}'>
1782            <member type='way' role='some' ref='#{way.id}'/>
1783            <member type='node' role='some' ref='#{node.id}'/>
1784            <member type='relation' role='some' ref='#{relation.id}'/>
1785           </relation>
1786          </create>
1787         </osmChange>
1788       CHANGESET
1789
1790       # upload it
1791       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1792       assert_response :too_many_requests, "upload did not hit rate limit"
1793     end
1794
1795     ##
1796     # test maximum rate limit
1797     def test_upload_maximum_rate_limit
1798       # create a user
1799       user = create(:user)
1800
1801       # create some objects to use
1802       node = create(:node)
1803       way = create(:way_with_nodes, :nodes_count => 2)
1804       relation = create(:relation)
1805
1806       # create a changeset to establish our initial edit time
1807       changeset = create(:changeset, :user => user,
1808                                      :created_at => Time.now.utc - 28.days)
1809
1810       # create changeset to put us near the maximum rate limit
1811       total_changes = Settings.max_changes_per_hour - 2
1812       while total_changes.positive?
1813         changes = [total_changes, Changeset::MAX_ELEMENTS].min
1814         changeset = create(:changeset, :user => user,
1815                                        :created_at => Time.now.utc - 5.minutes,
1816                                        :num_changes => changes)
1817         total_changes -= changes
1818       end
1819
1820       # create authentication header
1821       auth_header = bearer_authorization_header user
1822
1823       # simple diff to create a node way and relation using placeholders
1824       diff = <<~CHANGESET
1825         <osmChange>
1826          <create>
1827           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1828            <tag k='foo' v='bar'/>
1829            <tag k='baz' v='bat'/>
1830           </node>
1831           <way id='-1' changeset='#{changeset.id}'>
1832            <nd ref='#{node.id}'/>
1833           </way>
1834          </create>
1835          <create>
1836           <relation id='-1' changeset='#{changeset.id}'>
1837            <member type='way' role='some' ref='#{way.id}'/>
1838            <member type='node' role='some' ref='#{node.id}'/>
1839            <member type='relation' role='some' ref='#{relation.id}'/>
1840           </relation>
1841          </create>
1842         </osmChange>
1843       CHANGESET
1844
1845       # upload it
1846       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1847       assert_response :too_many_requests, "upload did not hit rate limit"
1848     end
1849
1850     ##
1851     # test initial size limit
1852     def test_upload_initial_size_limit
1853       # create a user
1854       user = create(:user)
1855
1856       # create a changeset that puts us near the initial size limit
1857       changeset = create(:changeset, :user => user,
1858                                      :min_lat => (-0.5 * GeoRecord::SCALE).round, :min_lon => (0.5 * GeoRecord::SCALE).round,
1859                                      :max_lat => (0.5 * GeoRecord::SCALE).round, :max_lon => (2.5 * GeoRecord::SCALE).round)
1860
1861       # create authentication header
1862       auth_header = bearer_authorization_header user
1863
1864       # simple diff to create a node
1865       diff = <<~CHANGESET
1866         <osmChange>
1867          <create>
1868           <node id='-1' lon='0.9' lat='2.9' changeset='#{changeset.id}'>
1869            <tag k='foo' v='bar'/>
1870            <tag k='baz' v='bat'/>
1871           </node>
1872          </create>
1873         </osmChange>
1874       CHANGESET
1875
1876       # upload it
1877       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1878       assert_response :payload_too_large, "upload did not hit size limit"
1879     end
1880
1881     ##
1882     # test size limit after one week
1883     def test_upload_week_size_limit
1884       # create a user
1885       user = create(:user)
1886
1887       # create a changeset to establish our initial edit time
1888       create(:changeset, :user => user, :created_at => Time.now.utc - 7.days)
1889
1890       # create a changeset that puts us near the initial size limit
1891       changeset = create(:changeset, :user => user, :bbox => [0.5, -0.5, 2.5, 0.5])
1892
1893       # create authentication header
1894       auth_header = bearer_authorization_header user
1895
1896       # simple diff to create a node way and relation using placeholders
1897       diff = <<~CHANGESET
1898         <osmChange>
1899          <create>
1900           <node id='-1' lon='35' lat='35' changeset='#{changeset.id}'>
1901            <tag k='foo' v='bar'/>
1902            <tag k='baz' v='bat'/>
1903           </node>
1904          </create>
1905         </osmChange>
1906       CHANGESET
1907
1908       # upload it
1909       post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1910       assert_response :payload_too_large, "upload did not hit size limit"
1911     end
1912
1913     ##
1914     # when we make some simple changes we get the same changes back from the
1915     # diff download.
1916     def test_diff_download_simple
1917       node = create(:node)
1918
1919       ## First try with a non-public user, which should get a forbidden
1920       auth_header = bearer_authorization_header create(:user, :data_public => false)
1921
1922       # create a temporary changeset
1923       xml = "<osm><changeset>" \
1924             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1925             "</changeset></osm>"
1926       post api_changesets_path, :params => xml, :headers => auth_header
1927       assert_response :forbidden
1928
1929       ## Now try with a normal user
1930       auth_header = bearer_authorization_header
1931
1932       # create a temporary changeset
1933       xml = "<osm><changeset>" \
1934             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1935             "</changeset></osm>"
1936       post api_changesets_path, :params => xml, :headers => auth_header
1937       assert_response :success
1938       changeset_id = @response.body.to_i
1939
1940       # add a diff to it
1941       diff = <<~CHANGESET
1942         <osmChange>
1943          <modify>
1944           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset_id}' version='1'/>
1945           <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset_id}' version='2'/>
1946           <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset_id}' version='3'/>
1947           <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset_id}' version='4'/>
1948           <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset_id}' version='5'/>
1949           <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset_id}' version='6'/>
1950           <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset_id}' version='7'/>
1951           <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset_id}' version='8'/>
1952          </modify>
1953         </osmChange>
1954       CHANGESET
1955
1956       # upload it
1957       post api_changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
1958       assert_response :success,
1959                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1960
1961       get api_changeset_download_path(changeset_id)
1962       assert_response :success
1963
1964       assert_select "osmChange", 1
1965       assert_select "osmChange>modify", 8
1966       assert_select "osmChange>modify>node", 8
1967     end
1968
1969     ##
1970     # culled this from josm to ensure that nothing in the way that josm
1971     # is formatting the request is causing it to fail.
1972     #
1973     # NOTE: the error turned out to be something else completely!
1974     def test_josm_upload
1975       auth_header = bearer_authorization_header
1976
1977       # create a temporary changeset
1978       xml = "<osm><changeset>" \
1979             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1980             "</changeset></osm>"
1981       post api_changesets_path, :params => xml, :headers => auth_header
1982       assert_response :success
1983       changeset_id = @response.body.to_i
1984
1985       diff = <<~OSMFILE
1986         <osmChange version="0.6" generator="JOSM">
1987         <create version="0.6" generator="JOSM">
1988           <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
1989           <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
1990           <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
1991           <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
1992           <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
1993           <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
1994           <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
1995           <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
1996           <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
1997           <way id='-10' action='modify' visible='true' changeset='#{changeset_id}'>
1998             <nd ref='-1' />
1999             <nd ref='-2' />
2000             <nd ref='-3' />
2001             <nd ref='-4' />
2002             <nd ref='-5' />
2003             <nd ref='-6' />
2004             <nd ref='-7' />
2005             <nd ref='-8' />
2006             <nd ref='-9' />
2007             <tag k='highway' v='residential' />
2008             <tag k='name' v='Foobar Street' />
2009           </way>
2010         </create>
2011         </osmChange>
2012       OSMFILE
2013
2014       # upload it
2015       post api_changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
2016       assert_response :success,
2017                       "can't upload a diff from JOSM: #{@response.body}"
2018
2019       get api_changeset_download_path(changeset_id)
2020       assert_response :success
2021
2022       assert_select "osmChange", 1
2023       assert_select "osmChange>create>node", 9
2024       assert_select "osmChange>create>way", 1
2025       assert_select "osmChange>create>way>nd", 9
2026       assert_select "osmChange>create>way>tag", 2
2027     end
2028
2029     ##
2030     # when we make some complex changes we get the same changes back from the
2031     # diff download.
2032     def test_diff_download_complex
2033       node = create(:node)
2034       node2 = create(:node)
2035       way = create(:way)
2036       auth_header = bearer_authorization_header
2037
2038       # create a temporary changeset
2039       xml = "<osm><changeset>" \
2040             "<tag k='created_by' v='osm test suite checking changesets'/>" \
2041             "</changeset></osm>"
2042       post api_changesets_path, :params => xml, :headers => auth_header
2043       assert_response :success
2044       changeset_id = @response.body.to_i
2045
2046       # add a diff to it
2047       diff = <<~CHANGESET
2048         <osmChange>
2049          <delete>
2050           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset_id}' version='1'/>
2051          </delete>
2052          <create>
2053           <node id='-1' lon='0.9' lat='0.9' changeset='#{changeset_id}' version='0'/>
2054           <node id='-2' lon='0.8' lat='0.9' changeset='#{changeset_id}' version='0'/>
2055           <node id='-3' lon='0.7' lat='0.9' changeset='#{changeset_id}' version='0'/>
2056          </create>
2057          <modify>
2058           <node id='#{node2.id}' lon='2.0' lat='1.5' changeset='#{changeset_id}' version='1'/>
2059           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
2060            <nd ref='#{node2.id}'/>
2061            <nd ref='-1'/>
2062            <nd ref='-2'/>
2063            <nd ref='-3'/>
2064           </way>
2065          </modify>
2066         </osmChange>
2067       CHANGESET
2068
2069       # upload it
2070       post api_changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
2071       assert_response :success,
2072                       "can't upload multiple versions of an element in a diff: #{@response.body}"
2073
2074       get api_changeset_download_path(changeset_id)
2075       assert_response :success
2076
2077       assert_select "osmChange", 1
2078       assert_select "osmChange>create", 3
2079       assert_select "osmChange>delete", 1
2080       assert_select "osmChange>modify", 2
2081       assert_select "osmChange>create>node", 3
2082       assert_select "osmChange>delete>node", 1
2083       assert_select "osmChange>modify>node", 1
2084       assert_select "osmChange>modify>way", 1
2085     end
2086
2087     ##
2088     # check that the bounding box of a changeset gets updated correctly
2089     # FIXME: This should really be moded to a integration test due to the with_controller
2090     def test_changeset_bbox
2091       way = create(:way)
2092       create(:way_node, :way => way, :node => create(:node, :lat => 0.3, :lon => 0.3))
2093
2094       auth_header = bearer_authorization_header
2095
2096       # create a new changeset
2097       xml = "<osm><changeset/></osm>"
2098       post api_changesets_path, :params => xml, :headers => auth_header
2099       assert_response :success, "Creating of changeset failed."
2100       changeset_id = @response.body.to_i
2101
2102       # add a single node to it
2103       with_controller(NodesController.new) do
2104         xml = "<osm><node lon='0.1' lat='0.2' changeset='#{changeset_id}'/></osm>"
2105         post api_nodes_path, :params => xml, :headers => auth_header
2106         assert_response :success, "Couldn't create node."
2107       end
2108
2109       # get the bounding box back from the changeset
2110       get api_changeset_path(changeset_id)
2111       assert_response :success, "Couldn't read back changeset."
2112       assert_select "osm>changeset[min_lon='0.1000000']", 1
2113       assert_select "osm>changeset[max_lon='0.1000000']", 1
2114       assert_select "osm>changeset[min_lat='0.2000000']", 1
2115       assert_select "osm>changeset[max_lat='0.2000000']", 1
2116
2117       # add another node to it
2118       with_controller(NodesController.new) do
2119         xml = "<osm><node lon='0.2' lat='0.1' changeset='#{changeset_id}'/></osm>"
2120         post api_nodes_path, :params => xml, :headers => auth_header
2121         assert_response :success, "Couldn't create second node."
2122       end
2123
2124       # get the bounding box back from the changeset
2125       get api_changeset_path(changeset_id)
2126       assert_response :success, "Couldn't read back changeset for the second time."
2127       assert_select "osm>changeset[min_lon='0.1000000']", 1
2128       assert_select "osm>changeset[max_lon='0.2000000']", 1
2129       assert_select "osm>changeset[min_lat='0.1000000']", 1
2130       assert_select "osm>changeset[max_lat='0.2000000']", 1
2131
2132       # add (delete) a way to it, which contains a point at (3,3)
2133       with_controller(WaysController.new) do
2134         xml = update_changeset(xml_for_way(way), changeset_id)
2135         delete api_way_path(way), :params => xml.to_s, :headers => auth_header
2136         assert_response :success, "Couldn't delete a way."
2137       end
2138
2139       # get the bounding box back from the changeset
2140       get api_changeset_path(changeset_id)
2141       assert_response :success, "Couldn't read back changeset for the third time."
2142       assert_select "osm>changeset[min_lon='0.1000000']", 1
2143       assert_select "osm>changeset[max_lon='0.3000000']", 1
2144       assert_select "osm>changeset[min_lat='0.1000000']", 1
2145       assert_select "osm>changeset[max_lat='0.3000000']", 1
2146     end
2147
2148     ##
2149     # check updating tags on a changeset
2150     def test_changeset_update
2151       private_user = create(:user, :data_public => false)
2152       private_changeset = create(:changeset, :user => private_user)
2153       user = create(:user)
2154       changeset = create(:changeset, :user => user)
2155
2156       ## First try with a non-public user
2157       new_changeset = create_changeset_xml(:user => private_user)
2158       new_tag = XML::Node.new "tag"
2159       new_tag["k"] = "tagtesting"
2160       new_tag["v"] = "valuetesting"
2161       new_changeset.find("//osm/changeset").first << new_tag
2162
2163       # try without any authorization
2164       put api_changeset_path(private_changeset), :params => new_changeset.to_s
2165       assert_response :unauthorized
2166
2167       # try with the wrong authorization
2168       auth_header = bearer_authorization_header
2169       put api_changeset_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2170       assert_response :conflict
2171
2172       # now this should get an unauthorized
2173       auth_header = bearer_authorization_header private_user
2174       put api_changeset_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2175       assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
2176
2177       ## Now try with the public user
2178       new_changeset = create_changeset_xml(:id => 1)
2179       new_tag = XML::Node.new "tag"
2180       new_tag["k"] = "tagtesting"
2181       new_tag["v"] = "valuetesting"
2182       new_changeset.find("//osm/changeset").first << new_tag
2183
2184       # try without any authorization
2185       put api_changeset_path(changeset), :params => new_changeset.to_s
2186       assert_response :unauthorized
2187
2188       # try with the wrong authorization
2189       auth_header = bearer_authorization_header
2190       put api_changeset_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2191       assert_response :conflict
2192
2193       # now this should work...
2194       auth_header = bearer_authorization_header user
2195       put api_changeset_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2196       assert_response :success
2197
2198       assert_select "osm>changeset[id='#{changeset.id}']", 1
2199       assert_select "osm>changeset>tag", 1
2200       assert_select "osm>changeset>tag[k='tagtesting'][v='valuetesting']", 1
2201     end
2202
2203     ##
2204     # check that a user different from the one who opened the changeset
2205     # can't modify it.
2206     def test_changeset_update_invalid
2207       auth_header = bearer_authorization_header
2208
2209       changeset = create(:changeset)
2210       new_changeset = create_changeset_xml(:user => changeset.user, :id => changeset.id)
2211       new_tag = XML::Node.new "tag"
2212       new_tag["k"] = "testing"
2213       new_tag["v"] = "testing"
2214       new_changeset.find("//osm/changeset").first << new_tag
2215
2216       put api_changeset_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2217       assert_response :conflict
2218     end
2219
2220     ##
2221     # check that a changeset can contain a certain max number of changes.
2222     ## FIXME should be changed to an integration test due to the with_controller
2223     def test_changeset_limits
2224       user = create(:user)
2225       auth_header = bearer_authorization_header user
2226
2227       # create an old changeset to ensure we have the maximum rate limit
2228       create(:changeset, :user => user, :created_at => Time.now.utc - 28.days)
2229
2230       # open a new changeset
2231       xml = "<osm><changeset/></osm>"
2232       post api_changesets_path, :params => xml, :headers => auth_header
2233       assert_response :success, "can't create a new changeset"
2234       cs_id = @response.body.to_i
2235
2236       # start the counter just short of where the changeset should finish.
2237       offset = 10
2238       # alter the database to set the counter on the changeset directly,
2239       # otherwise it takes about 6 minutes to fill all of them.
2240       changeset = Changeset.find(cs_id)
2241       changeset.num_changes = Changeset::MAX_ELEMENTS - offset
2242       changeset.save!
2243
2244       with_controller(NodesController.new) do
2245         # create a new node
2246         xml = "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
2247         post api_nodes_path, :params => xml, :headers => auth_header
2248         assert_response :success, "can't create a new node"
2249         node_id = @response.body.to_i
2250
2251         get api_node_path(node_id)
2252         assert_response :success, "can't read back new node"
2253         node_doc = XML::Parser.string(@response.body).parse
2254         node_xml = node_doc.find("//osm/node").first
2255
2256         # loop until we fill the changeset with nodes
2257         offset.times do |i|
2258           node_xml["lat"] = rand.to_s
2259           node_xml["lon"] = rand.to_s
2260           node_xml["version"] = (i + 1).to_s
2261
2262           put api_node_path(node_id), :params => node_doc.to_s, :headers => auth_header
2263           assert_response :success, "attempt #{i} should have succeeded"
2264         end
2265
2266         # trying again should fail
2267         node_xml["lat"] = rand.to_s
2268         node_xml["lon"] = rand.to_s
2269         node_xml["version"] = offset.to_s
2270
2271         put api_node_path(node_id), :params => node_doc.to_s, :headers => auth_header
2272         assert_response :conflict, "final attempt should have failed"
2273       end
2274
2275       changeset = Changeset.find(cs_id)
2276       assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
2277
2278       # check that the changeset is now closed as well
2279       assert_not(changeset.open?,
2280                  "changeset should have been auto-closed by exceeding " \
2281                  "element limit.")
2282     end
2283
2284     private
2285
2286     ##
2287     # check that the output consists of one specific changeset
2288     def assert_single_changeset(changeset, &)
2289       assert_dom "> changeset", 1 do
2290         assert_dom "> @id", changeset.id.to_s
2291         assert_dom "> @created_at", changeset.created_at.xmlschema
2292         if changeset.open?
2293           assert_dom "> @open", "true"
2294           assert_dom "> @closed_at", 0
2295         else
2296           assert_dom "> @open", "false"
2297           assert_dom "> @closed_at", changeset.closed_at.xmlschema
2298         end
2299         assert_dom "> @comments_count", changeset.comments.length.to_s
2300         assert_dom "> @changes_count", changeset.num_changes.to_s
2301         yield if block_given?
2302       end
2303     end
2304
2305     def assert_single_changeset_json(changeset, js)
2306       assert_equal changeset.id, js["changeset"]["id"]
2307       assert_equal changeset.created_at.xmlschema, js["changeset"]["created_at"]
2308       if changeset.open?
2309         assert js["changeset"]["open"]
2310         assert_nil js["changeset"]["closed_at"]
2311       else
2312         assert_not js["changeset"]["open"]
2313         assert_equal changeset.closed_at.xmlschema, js["changeset"]["closed_at"]
2314       end
2315       assert_equal changeset.comments.length, js["changeset"]["comments_count"]
2316       assert_equal changeset.num_changes, js["changeset"]["changes_count"]
2317     end
2318
2319     ##
2320     # check that certain changesets exist in the output in the specified order
2321     def assert_changesets_in_order(changesets)
2322       assert_select "osm>changeset", changesets.size
2323       changesets.each_with_index do |changeset, index|
2324         assert_select "osm>changeset:nth-child(#{index + 1})[id='#{changeset.id}']", 1
2325       end
2326     end
2327
2328     ##
2329     # update the changeset_id of a way element
2330     def update_changeset(xml, changeset_id)
2331       xml_attr_rewrite(xml, "changeset", changeset_id)
2332     end
2333
2334     ##
2335     # update an attribute in a way element
2336     def xml_attr_rewrite(xml, name, value)
2337       xml.find("//osm/way").first[name] = value.to_s
2338       xml
2339     end
2340
2341     ##
2342     # build XML for changesets
2343     def create_changeset_xml(user: nil, id: nil)
2344       root = XML::Document.new
2345       root.root = XML::Node.new "osm"
2346       cs = XML::Node.new "changeset"
2347       if user
2348         cs["user"] = user.display_name
2349         cs["uid"] = user.id.to_s
2350       end
2351       cs["id"] = id.to_s if id
2352       root.root << cs
2353       root
2354     end
2355   end
2356 end