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