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