]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets_controller_test.rb
Merge remote-tracking branch 'upstream/pull/5923'
[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, :bbox => [5, 5, 15, 15])
57       changeset3 = create(:changeset, :bbox => [4.5, 4.5, 5, 5])
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       changeset = create(:changeset, :bbox => [5, -5, 12, 15])
631
632       get api_changeset_path(changeset, :format => "json")
633       assert_response :success, "cannot get first changeset"
634
635       js = ActiveSupport::JSON.decode(@response.body)
636       assert_not_nil js
637       assert_equal(-5, js["changeset"]["min_lat"])
638       assert_equal 5, js["changeset"]["min_lon"]
639       assert_equal 15, js["changeset"]["max_lat"]
640       assert_equal 12, js["changeset"]["max_lon"]
641     end
642
643     ##
644     # check that a changeset that doesn't exist returns an appropriate message
645     def test_show_not_found
646       [0, -32, 233455644, "afg", "213"].each do |id|
647         get api_changeset_path(id)
648         assert_response :not_found, "should get a not found"
649       rescue ActionController::UrlGenerationError => e
650         assert_match(/No route matches/, e.to_s)
651       end
652     end
653
654     ##
655     # test that the user who opened a change can close it
656     def test_close
657       private_user = create(:user, :data_public => false)
658       private_changeset = create(:changeset, :user => private_user)
659       user = create(:user)
660       changeset = create(:changeset, :user => user)
661
662       ## Try without authentication
663       put changeset_close_path(changeset)
664       assert_response :unauthorized
665
666       ## Try using the non-public user
667       auth_header = bearer_authorization_header private_user
668       put changeset_close_path(private_changeset), :headers => auth_header
669       assert_require_public_data
670
671       ## The try with the public user
672       auth_header = bearer_authorization_header user
673
674       cs_id = changeset.id
675       put changeset_close_path(cs_id), :headers => auth_header
676       assert_response :success
677
678       # test that it really is closed now
679       cs = Changeset.find(changeset.id)
680       assert_not(cs.open?,
681                  "changeset should be closed now (#{cs.closed_at} > #{Time.now.utc}.")
682     end
683
684     ##
685     # test that a different user can't close another user's changeset
686     def test_close_invalid
687       user = create(:user)
688       changeset = create(:changeset)
689
690       auth_header = bearer_authorization_header user
691
692       put changeset_close_path(changeset), :headers => auth_header
693       assert_response :conflict
694       assert_equal "The user doesn't own that changeset", @response.body
695     end
696
697     ##
698     # test that you can't close using another method
699     def test_close_method_invalid
700       user = create(:user)
701       changeset = create(:changeset, :user => user)
702
703       auth_header = bearer_authorization_header user
704
705       get changeset_close_path(changeset), :headers => auth_header
706       assert_response :not_found
707       assert_template "rescues/routing_error"
708
709       post changeset_close_path(changeset), :headers => auth_header
710       assert_response :not_found
711       assert_template "rescues/routing_error"
712     end
713
714     ##
715     # check that you can't close a changeset that isn't found
716     def test_close_not_found
717       cs_ids = [0, -132, "123"]
718
719       # First try to do it with no auth
720       cs_ids.each do |id|
721         put changeset_close_path(id)
722         assert_response :unauthorized, "Shouldn't be able close the non-existant changeset #{id}, when not authorized"
723       rescue ActionController::UrlGenerationError => e
724         assert_match(/No route matches/, e.to_s)
725       end
726
727       # Now try with auth
728       auth_header = bearer_authorization_header
729       cs_ids.each do |id|
730         put changeset_close_path(id), :headers => auth_header
731         assert_response :not_found, "The changeset #{id} doesn't exist, so can't be closed"
732       rescue ActionController::UrlGenerationError => e
733         assert_match(/No route matches/, e.to_s)
734       end
735     end
736
737     ##
738     # upload something simple, but valid and check that it can
739     # be read back ok
740     # Also try without auth and another user.
741     def test_upload_simple_valid
742       private_user = create(:user, :data_public => false)
743       private_changeset = create(:changeset, :user => private_user)
744       user = create(:user)
745       changeset = create(:changeset, :user => user)
746
747       node = create(:node)
748       way = create(:way)
749       relation = create(:relation)
750       other_relation = create(:relation)
751       # create some tags, since we test that they are removed later
752       create(:node_tag, :node => node)
753       create(:way_tag, :way => way)
754       create(:relation_tag, :relation => relation)
755
756       ## Try with no auth
757       changeset_id = changeset.id
758
759       # simple diff to change a node, way and relation by removing
760       # their tags
761       diff = <<~CHANGESET
762         <osmChange>
763          <modify>
764           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
765           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
766            <nd ref='#{node.id}'/>
767           </way>
768          </modify>
769          <modify>
770           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
771            <member type='way' role='some' ref='#{way.id}'/>
772            <member type='node' role='some' ref='#{node.id}'/>
773            <member type='relation' role='some' ref='#{other_relation.id}'/>
774           </relation>
775          </modify>
776         </osmChange>
777       CHANGESET
778
779       # upload it
780       post changeset_upload_path(changeset), :params => diff
781       assert_response :unauthorized,
782                       "shouldn't be able to upload a simple valid diff to changeset: #{@response.body}"
783
784       ## Now try with a private user
785       auth_header = bearer_authorization_header private_user
786       changeset_id = private_changeset.id
787
788       # simple diff to change a node, way and relation by removing
789       # their tags
790       diff = <<~CHANGESET
791         <osmChange>
792          <modify>
793           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
794           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
795            <nd ref='#{node.id}'/>
796           </way>
797          </modify>
798          <modify>
799           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
800            <member type='way' role='some' ref='#{way.id}'/>
801            <member type='node' role='some' ref='#{node.id}'/>
802            <member type='relation' role='some' ref='#{other_relation.id}'/>
803           </relation>
804          </modify>
805         </osmChange>
806       CHANGESET
807
808       # upload it
809       post changeset_upload_path(private_changeset), :params => diff, :headers => auth_header
810       assert_response :forbidden,
811                       "can't upload a simple valid diff to changeset: #{@response.body}"
812
813       ## Now try with the public user
814       auth_header = bearer_authorization_header user
815       changeset_id = changeset.id
816
817       # simple diff to change a node, way and relation by removing
818       # their tags
819       diff = <<~CHANGESET
820         <osmChange>
821          <modify>
822           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
823           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
824            <nd ref='#{node.id}'/>
825           </way>
826          </modify>
827          <modify>
828           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
829            <member type='way' role='some' ref='#{way.id}'/>
830            <member type='node' role='some' ref='#{node.id}'/>
831            <member type='relation' role='some' ref='#{other_relation.id}'/>
832           </relation>
833          </modify>
834         </osmChange>
835       CHANGESET
836
837       # upload it
838       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
839       assert_response :success,
840                       "can't upload a simple valid diff to changeset: #{@response.body}"
841
842       # check that the changes made it into the database
843       assert_equal 0, Node.find(node.id).tags.size, "node #{node.id} should now have no tags"
844       assert_equal 0, Way.find(way.id).tags.size, "way #{way.id} should now have no tags"
845       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
846     end
847
848     ##
849     # upload something which creates new objects using placeholders
850     def test_upload_create_valid
851       user = create(:user)
852       changeset = create(:changeset, :user => user)
853       node = create(:node)
854       way = create(:way_with_nodes, :nodes_count => 2)
855       relation = create(:relation)
856
857       auth_header = bearer_authorization_header user
858
859       # simple diff to create a node way and relation using placeholders
860       diff = <<~CHANGESET
861         <osmChange>
862          <create>
863           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
864            <tag k='foo' v='bar'/>
865            <tag k='baz' v='bat'/>
866           </node>
867           <way id='-1' changeset='#{changeset.id}'>
868            <nd ref='#{node.id}'/>
869           </way>
870          </create>
871          <create>
872           <relation id='-1' changeset='#{changeset.id}'>
873            <member type='way' role='some' ref='#{way.id}'/>
874            <member type='node' role='some' ref='#{node.id}'/>
875            <member type='relation' role='some' ref='#{relation.id}'/>
876           </relation>
877          </create>
878         </osmChange>
879       CHANGESET
880
881       # upload it
882       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
883       assert_response :success,
884                       "can't upload a simple valid creation to changeset: #{@response.body}"
885
886       # check the returned payload
887       new_node_id, new_way_id, new_rel_id = nil
888       assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
889         # inspect the response to find out what the new element IDs are
890         # check the old IDs are all present and negative one
891         # check the versions are present and equal one
892         assert_dom "> node", 1 do |(node_el)|
893           new_node_id = node_el["new_id"].to_i
894           assert_dom "> @old_id", "-1"
895           assert_dom "> @new_version", "1"
896         end
897         assert_dom "> way", 1 do |(way_el)|
898           new_way_id = way_el["new_id"].to_i
899           assert_dom "> @old_id", "-1"
900           assert_dom "> @new_version", "1"
901         end
902         assert_dom "> relation", 1 do |(rel_el)|
903           new_rel_id = rel_el["new_id"].to_i
904           assert_dom "> @old_id", "-1"
905           assert_dom "> @new_version", "1"
906         end
907       end
908
909       # check that the changes made it into the database
910       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
911       assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
912       assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
913     end
914
915     ##
916     # test a complex delete where we delete elements which rely on eachother
917     # in the same transaction.
918     def test_upload_delete
919       changeset = create(:changeset)
920       super_relation = create(:relation)
921       used_relation = create(:relation)
922       used_way = create(:way)
923       used_node = create(:node)
924       create(:relation_member, :relation => super_relation, :member => used_relation)
925       create(:relation_member, :relation => super_relation, :member => used_way)
926       create(:relation_member, :relation => super_relation, :member => used_node)
927
928       auth_header = bearer_authorization_header changeset.user
929
930       diff = XML::Document.new
931       diff.root = XML::Node.new "osmChange"
932       delete = XML::Node.new "delete"
933       diff.root << delete
934       delete << xml_node_for_relation(super_relation)
935       delete << xml_node_for_relation(used_relation)
936       delete << xml_node_for_way(used_way)
937       delete << xml_node_for_node(used_node)
938
939       # update the changeset to one that this user owns
940       %w[node way relation].each do |type|
941         delete.find("//osmChange/delete/#{type}").each do |n|
942           n["changeset"] = changeset.id.to_s
943         end
944       end
945
946       # upload it
947       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
948       assert_response :success,
949                       "can't upload a deletion diff to changeset: #{@response.body}"
950
951       # check the response is well-formed
952       assert_select "diffResult>node", 1
953       assert_select "diffResult>way", 1
954       assert_select "diffResult>relation", 2
955
956       # check that everything was deleted
957       assert_not Node.find(used_node.id).visible
958       assert_not Way.find(used_way.id).visible
959       assert_not Relation.find(super_relation.id).visible
960       assert_not Relation.find(used_relation.id).visible
961     end
962
963     ##
964     # test uploading a delete with no lat/lon, as they are optional in
965     # the osmChange spec.
966     def test_upload_nolatlon_delete
967       node = create(:node)
968       changeset = create(:changeset)
969
970       auth_header = bearer_authorization_header changeset.user
971       diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
972
973       # upload it
974       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
975       assert_response :success,
976                       "can't upload a deletion diff to changeset: #{@response.body}"
977
978       # check the response is well-formed
979       assert_select "diffResult>node", 1
980
981       # check that everything was deleted
982       assert_not Node.find(node.id).visible
983     end
984
985     def test_repeated_changeset_create
986       3.times do
987         auth_header = bearer_authorization_header
988
989         # create a temporary changeset
990         xml = "<osm><changeset>" \
991               "<tag k='created_by' v='osm test suite checking changesets'/>" \
992               "</changeset></osm>"
993         assert_difference "Changeset.count", 1 do
994           post api_changesets_path, :params => xml, :headers => auth_header
995         end
996         assert_response :success
997       end
998     end
999
1000     def test_upload_large_changeset
1001       user = create(:user)
1002       auth_header = bearer_authorization_header user
1003
1004       # create an old changeset to ensure we have the maximum rate limit
1005       create(:changeset, :user => user, :created_at => Time.now.utc - 28.days)
1006
1007       # create a changeset
1008       post api_changesets_path, :params => "<osm><changeset/></osm>", :headers => auth_header
1009       assert_response :success, "Should be able to create a changeset: #{@response.body}"
1010       changeset_id = @response.body.to_i
1011
1012       # upload some widely-spaced nodes, spiralling positive and negative
1013       diff = <<~CHANGESET
1014         <osmChange>
1015          <create>
1016           <node id='-1' lon='-20' lat='-10' changeset='#{changeset_id}'/>
1017           <node id='-10' lon='20'  lat='10' changeset='#{changeset_id}'/>
1018           <node id='-2' lon='-40' lat='-20' changeset='#{changeset_id}'/>
1019           <node id='-11' lon='40'  lat='20' changeset='#{changeset_id}'/>
1020           <node id='-3' lon='-60' lat='-30' changeset='#{changeset_id}'/>
1021           <node id='-12' lon='60'  lat='30' changeset='#{changeset_id}'/>
1022           <node id='-4' lon='-80' lat='-40' changeset='#{changeset_id}'/>
1023           <node id='-13' lon='80'  lat='40' changeset='#{changeset_id}'/>
1024           <node id='-5' lon='-100' lat='-50' changeset='#{changeset_id}'/>
1025           <node id='-14' lon='100'  lat='50' changeset='#{changeset_id}'/>
1026           <node id='-6' lon='-120' lat='-60' changeset='#{changeset_id}'/>
1027           <node id='-15' lon='120'  lat='60' changeset='#{changeset_id}'/>
1028           <node id='-7' lon='-140' lat='-70' changeset='#{changeset_id}'/>
1029           <node id='-16' lon='140'  lat='70' changeset='#{changeset_id}'/>
1030           <node id='-8' lon='-160' lat='-80' changeset='#{changeset_id}'/>
1031           <node id='-17' lon='160'  lat='80' changeset='#{changeset_id}'/>
1032           <node id='-9' lon='-179.9' lat='-89.9' changeset='#{changeset_id}'/>
1033           <node id='-18' lon='179.9'  lat='89.9' changeset='#{changeset_id}'/>
1034          </create>
1035         </osmChange>
1036       CHANGESET
1037
1038       # upload it, which used to cause an error like "PGError: ERROR:
1039       # integer out of range" (bug #2152). but shouldn't any more.
1040       post changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
1041       assert_response :success,
1042                       "can't upload a spatially-large diff to changeset: #{@response.body}"
1043
1044       # check that the changeset bbox is within bounds
1045       cs = Changeset.find(changeset_id)
1046       assert_operator cs.min_lon, :>=, -180 * GeoRecord::SCALE, "Minimum longitude (#{cs.min_lon / GeoRecord::SCALE}) should be >= -180 to be valid."
1047       assert_operator cs.max_lon, :<=, 180 * GeoRecord::SCALE, "Maximum longitude (#{cs.max_lon / GeoRecord::SCALE}) should be <= 180 to be valid."
1048       assert_operator cs.min_lat, :>=, -90 * GeoRecord::SCALE, "Minimum latitude (#{cs.min_lat / GeoRecord::SCALE}) should be >= -90 to be valid."
1049       assert_operator cs.max_lat, :<=, 90 * GeoRecord::SCALE, "Maximum latitude (#{cs.max_lat / GeoRecord::SCALE}) should be <= 90 to be valid."
1050     end
1051
1052     ##
1053     # test that deleting stuff in a transaction doesn't bypass the checks
1054     # to ensure that used elements are not deleted.
1055     def test_upload_delete_invalid
1056       changeset = create(:changeset)
1057       relation = create(:relation)
1058       other_relation = create(:relation)
1059       used_way = create(:way)
1060       used_node = create(:node)
1061       create(:relation_member, :relation => relation, :member => used_way)
1062       create(:relation_member, :relation => relation, :member => used_node)
1063
1064       auth_header = bearer_authorization_header changeset.user
1065
1066       diff = XML::Document.new
1067       diff.root = XML::Node.new "osmChange"
1068       delete = XML::Node.new "delete"
1069       diff.root << delete
1070       delete << xml_node_for_relation(other_relation)
1071       delete << xml_node_for_way(used_way)
1072       delete << xml_node_for_node(used_node)
1073
1074       # update the changeset to one that this user owns
1075       %w[node way relation].each do |type|
1076         delete.find("//osmChange/delete/#{type}").each do |n|
1077           n["changeset"] = changeset.id.to_s
1078         end
1079       end
1080
1081       # upload it
1082       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1083       assert_response :precondition_failed,
1084                       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
1085       assert_equal "Precondition failed: Way #{used_way.id} is still used by relations #{relation.id}.", @response.body
1086
1087       # check that nothing was, in fact, deleted
1088       assert Node.find(used_node.id).visible
1089       assert Way.find(used_way.id).visible
1090       assert Relation.find(relation.id).visible
1091       assert Relation.find(other_relation.id).visible
1092     end
1093
1094     ##
1095     # test that a conditional delete of an in use object works.
1096     def test_upload_delete_if_unused
1097       changeset = create(:changeset)
1098       super_relation = create(:relation)
1099       used_relation = create(:relation)
1100       used_way = create(:way)
1101       used_node = create(:node)
1102       create(:relation_member, :relation => super_relation, :member => used_relation)
1103       create(:relation_member, :relation => super_relation, :member => used_way)
1104       create(:relation_member, :relation => super_relation, :member => used_node)
1105
1106       auth_header = bearer_authorization_header changeset.user
1107
1108       diff = XML::Document.new
1109       diff.root = XML::Node.new "osmChange"
1110       delete = XML::Node.new "delete"
1111       diff.root << delete
1112       delete["if-unused"] = ""
1113       delete << xml_node_for_relation(used_relation)
1114       delete << xml_node_for_way(used_way)
1115       delete << xml_node_for_node(used_node)
1116
1117       # update the changeset to one that this user owns
1118       %w[node way relation].each do |type|
1119         delete.find("//osmChange/delete/#{type}").each do |n|
1120           n["changeset"] = changeset.id.to_s
1121         end
1122       end
1123
1124       # upload it
1125       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1126       assert_response :success,
1127                       "can't do a conditional delete of in use objects: #{@response.body}"
1128
1129       # check the returned payload
1130       assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
1131         # check the old IDs are all present and what we expect
1132         # check the new IDs are all present and unchanged
1133         # check the new versions are all present and unchanged
1134         assert_dom "> node", 1 do
1135           assert_dom "> @old_id", used_node.id.to_s
1136           assert_dom "> @new_id", used_node.id.to_s
1137           assert_dom "> @new_version", used_node.version.to_s
1138         end
1139         assert_dom "> way", 1 do
1140           assert_dom "> @old_id", used_way.id.to_s
1141           assert_dom "> @new_id", used_way.id.to_s
1142           assert_dom "> @new_version", used_way.version.to_s
1143         end
1144         assert_dom "> relation", 1 do
1145           assert_dom "> @old_id", used_relation.id.to_s
1146           assert_dom "> @new_id", used_relation.id.to_s
1147           assert_dom "> @new_version", used_relation.version.to_s
1148         end
1149       end
1150
1151       # check that nothing was, in fact, deleted
1152       assert Node.find(used_node.id).visible
1153       assert Way.find(used_way.id).visible
1154       assert Relation.find(used_relation.id).visible
1155     end
1156
1157     ##
1158     # upload an element with a really long tag value
1159     def test_upload_invalid_too_long_tag
1160       changeset = create(:changeset)
1161
1162       auth_header = bearer_authorization_header changeset.user
1163
1164       # simple diff to create a node way and relation using placeholders
1165       diff = <<~CHANGESET
1166         <osmChange>
1167          <create>
1168           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1169            <tag k='foo' v='#{'x' * 256}'/>
1170           </node>
1171          </create>
1172         </osmChange>
1173       CHANGESET
1174
1175       # upload it
1176       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1177       assert_response :bad_request,
1178                       "shouldn't be able to upload too long a tag to changeset: #{@response.body}"
1179     end
1180
1181     ##
1182     # upload something which creates new objects and inserts them into
1183     # existing containers using placeholders.
1184     def test_upload_complex
1185       way = create(:way)
1186       node = create(:node)
1187       relation = create(:relation)
1188       create(:way_node, :way => way, :node => node)
1189
1190       changeset = create(:changeset)
1191
1192       auth_header = bearer_authorization_header changeset.user
1193
1194       # simple diff to create a node way and relation using placeholders
1195       diff = <<~CHANGESET
1196         <osmChange>
1197          <create>
1198           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1199            <tag k='foo' v='bar'/>
1200            <tag k='baz' v='bat'/>
1201           </node>
1202          </create>
1203          <modify>
1204           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
1205            <nd ref='-1'/>
1206            <nd ref='#{node.id}'/>
1207           </way>
1208           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
1209            <member type='way' role='some' ref='#{way.id}'/>
1210            <member type='node' role='some' ref='-1'/>
1211            <member type='relation' role='some' ref='#{relation.id}'/>
1212           </relation>
1213          </modify>
1214         </osmChange>
1215       CHANGESET
1216
1217       # upload it
1218       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1219       assert_response :success,
1220                       "can't upload a complex diff to changeset: #{@response.body}"
1221
1222       # check the returned payload
1223       new_node_id = nil
1224       assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
1225         assert_dom "> node", 1 do |(node_el)|
1226           new_node_id = node_el["new_id"].to_i
1227         end
1228         assert_dom "> way", 1
1229         assert_dom "> relation", 1
1230       end
1231
1232       # check that the changes made it into the database
1233       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
1234       assert_equal [new_node_id, node.id], Way.find(way.id).nds, "way nodes should match"
1235       Relation.find(relation.id).members.each do |type, id, _role|
1236         assert_equal new_node_id, id, "relation should contain new node" if type == "node"
1237       end
1238     end
1239
1240     ##
1241     # create a diff which references several changesets, which should cause
1242     # a rollback and none of the diff gets committed
1243     def test_upload_invalid_changesets
1244       changeset = create(:changeset)
1245       other_changeset = create(:changeset, :user => changeset.user)
1246       node = create(:node)
1247       way = create(:way)
1248       relation = create(:relation)
1249       other_relation = create(:relation)
1250
1251       auth_header = bearer_authorization_header changeset.user
1252
1253       # simple diff to create a node way and relation using placeholders
1254       diff = <<~CHANGESET
1255         <osmChange>
1256          <modify>
1257           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1258           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
1259            <nd ref='#{node.id}'/>
1260           </way>
1261          </modify>
1262          <modify>
1263           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
1264            <member type='way' role='some' ref='#{way.id}'/>
1265            <member type='node' role='some' ref='#{node.id}'/>
1266            <member type='relation' role='some' ref='#{other_relation.id}'/>
1267           </relation>
1268          </modify>
1269          <create>
1270           <node id='-1' lon='0' lat='0' changeset='#{other_changeset.id}'>
1271            <tag k='foo' v='bar'/>
1272            <tag k='baz' v='bat'/>
1273           </node>
1274          </create>
1275         </osmChange>
1276       CHANGESET
1277
1278       # upload it
1279       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1280       assert_response :conflict,
1281                       "uploading a diff with multiple changesets should have failed"
1282
1283       # check that objects are unmodified
1284       assert_nodes_are_equal(node, Node.find(node.id))
1285       assert_ways_are_equal(way, Way.find(way.id))
1286       assert_relations_are_equal(relation, Relation.find(relation.id))
1287     end
1288
1289     ##
1290     # upload multiple versions of the same element in the same diff.
1291     def test_upload_multiple_valid
1292       node = create(:node)
1293       changeset = create(:changeset)
1294       auth_header = bearer_authorization_header changeset.user
1295
1296       # change the location of a node multiple times, each time referencing
1297       # the last version. doesn't this depend on version numbers being
1298       # sequential?
1299       diff = <<~CHANGESET
1300         <osmChange>
1301          <modify>
1302           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset.id}' version='1'/>
1303           <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset.id}' version='2'/>
1304           <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset.id}' version='3'/>
1305           <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset.id}' version='4'/>
1306           <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset.id}' version='5'/>
1307           <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset.id}' version='6'/>
1308           <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset.id}' version='7'/>
1309           <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset.id}' version='8'/>
1310          </modify>
1311         </osmChange>
1312       CHANGESET
1313
1314       # upload it
1315       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1316       assert_response :success,
1317                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1318
1319       # check the response is well-formed. its counter-intuitive, but the
1320       # API will return multiple elements with the same ID and different
1321       # version numbers for each change we made.
1322       assert_select "diffResult>node", 8
1323     end
1324
1325     ##
1326     # upload multiple versions of the same element in the same diff, but
1327     # keep the version numbers the same.
1328     def test_upload_multiple_duplicate
1329       node = create(:node)
1330       changeset = create(:changeset)
1331
1332       auth_header = bearer_authorization_header changeset.user
1333
1334       diff = <<~CHANGESET
1335         <osmChange>
1336          <modify>
1337           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1338           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1339          </modify>
1340         </osmChange>
1341       CHANGESET
1342
1343       # upload it
1344       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1345       assert_response :conflict,
1346                       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
1347     end
1348
1349     ##
1350     # try to upload some elements without specifying the version
1351     def test_upload_missing_version
1352       changeset = create(:changeset)
1353
1354       auth_header = bearer_authorization_header changeset.user
1355
1356       diff = <<~CHANGESET
1357         <osmChange>
1358          <modify>
1359          <node id='1' lon='1' lat='1' changeset='#{changeset.id}'/>
1360          </modify>
1361         </osmChange>
1362       CHANGESET
1363
1364       # upload it
1365       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1366       assert_response :bad_request,
1367                       "shouldn't be able to upload an element without version: #{@response.body}"
1368     end
1369
1370     ##
1371     # try to upload with commands other than create, modify, or delete
1372     def test_action_upload_invalid
1373       changeset = create(:changeset)
1374
1375       auth_header = bearer_authorization_header changeset.user
1376
1377       diff = <<~CHANGESET
1378         <osmChange>
1379           <ping>
1380            <node id='1' lon='1' lat='1' changeset='#{changeset.id}' />
1381           </ping>
1382         </osmChange>
1383       CHANGESET
1384       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1385       assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
1386       assert_equal("Unknown action ping, choices are create, modify, delete", @response.body)
1387     end
1388
1389     ##
1390     # upload a valid changeset which has a mixture of whitespace
1391     # to check a bug reported by ivansanchez (#1565).
1392     def test_upload_whitespace_valid
1393       changeset = create(:changeset)
1394       node = create(:node)
1395       way = create(:way_with_nodes, :nodes_count => 2)
1396       relation = create(:relation)
1397       other_relation = create(:relation)
1398       create(:relation_tag, :relation => relation)
1399
1400       auth_header = bearer_authorization_header changeset.user
1401
1402       diff = <<~CHANGESET
1403         <osmChange>
1404          <modify><node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}'
1405           version='1'></node>
1406           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='2'><tag k='k' v='v'/></node></modify>
1407          <modify>
1408          <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'><member
1409            type='way' role='some' ref='#{way.id}'/><member
1410             type='node' role='some' ref='#{node.id}'/>
1411            <member type='relation' role='some' ref='#{other_relation.id}'/>
1412           </relation>
1413          </modify></osmChange>
1414       CHANGESET
1415
1416       # upload it
1417       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1418       assert_response :success,
1419                       "can't upload a valid diff with whitespace variations to changeset: #{@response.body}"
1420
1421       # check the response is well-formed
1422       assert_select "diffResult>node", 2
1423       assert_select "diffResult>relation", 1
1424
1425       # check that the changes made it into the database
1426       assert_equal 1, Node.find(node.id).tags.size, "node #{node.id} should now have one tag"
1427       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
1428     end
1429
1430     ##
1431     # test that a placeholder can be reused within the same upload.
1432     def test_upload_reuse_placeholder_valid
1433       changeset = create(:changeset)
1434
1435       auth_header = bearer_authorization_header changeset.user
1436
1437       diff = <<~CHANGESET
1438         <osmChange>
1439          <create>
1440           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1441            <tag k="foo" v="bar"/>
1442           </node>
1443          </create>
1444          <modify>
1445           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1446          </modify>
1447          <delete>
1448           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
1449          </delete>
1450         </osmChange>
1451       CHANGESET
1452
1453       # upload it
1454       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1455       assert_response :success,
1456                       "can't upload a valid diff with re-used placeholders to changeset: #{@response.body}"
1457
1458       # check the response is well-formed
1459       assert_select "diffResult>node", 3
1460       assert_select "diffResult>node[old_id='-1']", 3
1461     end
1462
1463     ##
1464     # test what happens if a diff upload re-uses placeholder IDs in an
1465     # illegal way.
1466     def test_upload_placeholder_invalid
1467       changeset = create(:changeset)
1468
1469       auth_header = bearer_authorization_header changeset.user
1470
1471       diff = <<~CHANGESET
1472         <osmChange>
1473          <create>
1474           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1475           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1476           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
1477          </create>
1478         </osmChange>
1479       CHANGESET
1480
1481       # upload it
1482       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1483       assert_response :bad_request,
1484                       "shouldn't be able to re-use placeholder IDs"
1485
1486       # placeholder_ids must be unique across all action blocks
1487       diff = <<~CHANGESET
1488         <osmChange>
1489          <create>
1490           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1491          </create>
1492          <create>
1493           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1494          </create>
1495         </osmChange>
1496       CHANGESET
1497
1498       # upload it
1499       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1500       assert_response :bad_request,
1501                       "shouldn't be able to re-use placeholder IDs"
1502     end
1503
1504     def test_upload_process_order
1505       changeset = create(:changeset)
1506
1507       auth_header = bearer_authorization_header changeset.user
1508
1509       diff = <<~CHANGESET
1510         <osmChange>
1511         <create>
1512           <node id="-1" lat="1" lon="2" changeset="#{changeset.id}"/>
1513           <way id="-1" changeset="#{changeset.id}">
1514               <nd ref="-1"/>
1515               <nd ref="-2"/>
1516           </way>
1517           <node id="-2" lat="1" lon="2" changeset="#{changeset.id}"/>
1518         </create>
1519         </osmChange>
1520       CHANGESET
1521
1522       # upload it
1523       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1524       assert_response :bad_request,
1525                       "shouldn't refer elements behind it"
1526     end
1527
1528     def test_upload_duplicate_delete
1529       changeset = create(:changeset)
1530
1531       auth_header = bearer_authorization_header changeset.user
1532
1533       diff = <<~CHANGESET
1534         <osmChange>
1535           <create>
1536             <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1537           </create>
1538           <delete>
1539             <node id="-1" version="1" changeset="#{changeset.id}" />
1540             <node id="-1" version="1" changeset="#{changeset.id}" />
1541           </delete>
1542         </osmChange>
1543       CHANGESET
1544
1545       # upload it
1546       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1547       assert_response :gone,
1548                       "transaction should be cancelled by second deletion"
1549
1550       diff = <<~CHANGESET
1551         <osmChange>
1552           <create>
1553             <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1554           </create>
1555           <delete if-unused="true">
1556             <node id="-1" version="1" changeset="#{changeset.id}" />
1557             <node id="-1" version="1" changeset="#{changeset.id}" />
1558           </delete>
1559         </osmChange>
1560       CHANGESET
1561
1562       # upload it
1563       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1564
1565       assert_select "diffResult>node", 3
1566       assert_select "diffResult>node[old_id='-1']", 3
1567       assert_select "diffResult>node[new_version='1']", 1
1568       assert_select "diffResult>node[new_version='2']", 1
1569     end
1570
1571     ##
1572     # test that uploading a way referencing invalid placeholders gives a
1573     # proper error, not a 500.
1574     def test_upload_placeholder_invalid_way
1575       changeset = create(:changeset)
1576       way = create(:way)
1577
1578       auth_header = bearer_authorization_header changeset.user
1579
1580       diff = <<~CHANGESET
1581         <osmChange>
1582          <create>
1583           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1584           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1585           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1586           <way id="-1" changeset="#{changeset.id}" version="1">
1587            <nd ref="-1"/>
1588            <nd ref="-2"/>
1589            <nd ref="-3"/>
1590            <nd ref="-4"/>
1591           </way>
1592          </create>
1593         </osmChange>
1594       CHANGESET
1595
1596       # upload it
1597       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1598       assert_response :bad_request,
1599                       "shouldn't be able to use invalid placeholder IDs"
1600       assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
1601
1602       # the same again, but this time use an existing way
1603       diff = <<~CHANGESET
1604         <osmChange>
1605          <create>
1606           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1607           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1608           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1609           <way id="#{way.id}" changeset="#{changeset.id}" version="1">
1610            <nd ref="-1"/>
1611            <nd ref="-2"/>
1612            <nd ref="-3"/>
1613            <nd ref="-4"/>
1614           </way>
1615          </create>
1616         </osmChange>
1617       CHANGESET
1618
1619       # upload it
1620       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1621       assert_response :bad_request,
1622                       "shouldn't be able to use invalid placeholder IDs"
1623       assert_equal "Placeholder node not found for reference -4 in way #{way.id}", @response.body
1624     end
1625
1626     ##
1627     # test that uploading a relation referencing invalid placeholders gives a
1628     # proper error, not a 500.
1629     def test_upload_placeholder_invalid_relation
1630       changeset = create(:changeset)
1631       relation = create(:relation)
1632
1633       auth_header = bearer_authorization_header changeset.user
1634
1635       diff = <<~CHANGESET
1636         <osmChange>
1637          <create>
1638           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1639           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1640           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1641           <relation id="-1" changeset="#{changeset.id}" version="1">
1642            <member type="node" role="foo" ref="-1"/>
1643            <member type="node" role="foo" ref="-2"/>
1644            <member type="node" role="foo" ref="-3"/>
1645            <member type="node" role="foo" ref="-4"/>
1646           </relation>
1647          </create>
1648         </osmChange>
1649       CHANGESET
1650
1651       # upload it
1652       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1653       assert_response :bad_request,
1654                       "shouldn't be able to use invalid placeholder IDs"
1655       assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
1656
1657       # the same again, but this time use an existing relation
1658       diff = <<~CHANGESET
1659         <osmChange>
1660          <create>
1661           <node id="-1" lon="0.0" lat="0.0" changeset="#{changeset.id}" version="1"/>
1662           <node id="-2" lon="0.1" lat="0.1" changeset="#{changeset.id}" version="1"/>
1663           <node id="-3" lon="0.2" lat="0.2" changeset="#{changeset.id}" version="1"/>
1664           <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
1665            <member type="node" role="foo" ref="-1"/>
1666            <member type="node" role="foo" ref="-2"/>
1667            <member type="node" role="foo" ref="-3"/>
1668            <member type="way" role="bar" ref="-1"/>
1669           </relation>
1670          </create>
1671         </osmChange>
1672       CHANGESET
1673
1674       # upload it
1675       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1676       assert_response :bad_request,
1677                       "shouldn't be able to use invalid placeholder IDs"
1678       assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
1679     end
1680
1681     ##
1682     # test what happens if a diff is uploaded containing only a node
1683     # move.
1684     def test_upload_node_move
1685       auth_header = bearer_authorization_header
1686
1687       xml = "<osm><changeset>" \
1688             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1689             "</changeset></osm>"
1690       post api_changesets_path, :params => xml, :headers => auth_header
1691       assert_response :success
1692       changeset_id = @response.body.to_i
1693
1694       old_node = create(:node, :lat => 1, :lon => 1)
1695
1696       diff = XML::Document.new
1697       diff.root = XML::Node.new "osmChange"
1698       modify = XML::Node.new "modify"
1699       xml_old_node = xml_node_for_node(old_node)
1700       xml_old_node["lat"] = 2.0.to_s
1701       xml_old_node["lon"] = 2.0.to_s
1702       xml_old_node["changeset"] = changeset_id.to_s
1703       modify << xml_old_node
1704       diff.root << modify
1705
1706       # upload it
1707       post changeset_upload_path(changeset_id), :params => diff.to_s, :headers => auth_header
1708       assert_response :success,
1709                       "diff should have uploaded OK"
1710
1711       # check the bbox
1712       changeset = Changeset.find(changeset_id)
1713       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1714       assert_equal 2 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 2 degrees"
1715       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1716       assert_equal 2 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 2 degrees"
1717     end
1718
1719     ##
1720     # test what happens if a diff is uploaded adding a node to a way.
1721     def test_upload_way_extend
1722       auth_header = bearer_authorization_header
1723
1724       xml = "<osm><changeset>" \
1725             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1726             "</changeset></osm>"
1727       post api_changesets_path, :params => xml, :headers => auth_header
1728       assert_response :success
1729       changeset_id = @response.body.to_i
1730
1731       old_way = create(:way)
1732       create(:way_node, :way => old_way, :node => create(:node, :lat => 0.1, :lon => 0.1))
1733
1734       diff = XML::Document.new
1735       diff.root = XML::Node.new "osmChange"
1736       modify = XML::Node.new "modify"
1737       xml_old_way = xml_node_for_way(old_way)
1738       nd_ref = XML::Node.new "nd"
1739       nd_ref["ref"] = create(:node, :lat => 0.3, :lon => 0.3).id.to_s
1740       xml_old_way << nd_ref
1741       xml_old_way["changeset"] = changeset_id.to_s
1742       modify << xml_old_way
1743       diff.root << modify
1744
1745       # upload it
1746       post changeset_upload_path(changeset_id), :params => diff.to_s, :headers => auth_header
1747       assert_response :success,
1748                       "diff should have uploaded OK"
1749
1750       # check the bbox
1751       changeset = Changeset.find(changeset_id)
1752       assert_equal 0.1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 0.1 degree"
1753       assert_equal 0.3 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 0.3 degrees"
1754       assert_equal 0.1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 0.1 degree"
1755       assert_equal 0.3 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 0.3 degrees"
1756     end
1757
1758     ##
1759     # test for more issues in #1568
1760     def test_upload_empty_invalid
1761       changeset = create(:changeset)
1762
1763       auth_header = bearer_authorization_header changeset.user
1764
1765       ["<osmChange/>",
1766        "<osmChange></osmChange>",
1767        "<osmChange><modify/></osmChange>",
1768        "<osmChange><modify></modify></osmChange>"].each do |diff|
1769         # upload it
1770         post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1771         assert_response(:success, "should be able to upload " \
1772                                   "empty changeset: " + diff)
1773       end
1774     end
1775
1776     ##
1777     # test that the X-Error-Format header works to request XML errors
1778     def test_upload_xml_errors
1779       changeset = create(:changeset)
1780       node = create(:node)
1781       create(:relation_member, :member => node)
1782
1783       auth_header = bearer_authorization_header changeset.user
1784
1785       # try and delete a node that is in use
1786       diff = XML::Document.new
1787       diff.root = XML::Node.new "osmChange"
1788       delete = XML::Node.new "delete"
1789       diff.root << delete
1790       delete << xml_node_for_node(node)
1791
1792       # upload it
1793       error_header = error_format_header "xml"
1794       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header.merge(error_header)
1795       assert_response :success,
1796                       "failed to return error in XML format"
1797
1798       # check the returned payload
1799       assert_select "osmError[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
1800       assert_select "osmError>status", 1
1801       assert_select "osmError>message", 1
1802     end
1803
1804     def test_upload_not_found
1805       changeset = create(:changeset)
1806
1807       auth_header = bearer_authorization_header changeset.user
1808
1809       # modify node
1810       diff = <<~CHANGESET
1811         <osmChange>
1812         <modify>
1813           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1814         </modify>
1815         </osmChange>
1816       CHANGESET
1817
1818       # upload it
1819       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1820       assert_response :not_found, "Node should not be found"
1821
1822       # modify way
1823       diff = <<~CHANGESET
1824         <osmChange>
1825         <modify>
1826           <way id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1827         </modify>
1828         </osmChange>
1829       CHANGESET
1830
1831       # upload it
1832       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1833       assert_response :not_found, "Way should not be found"
1834
1835       # modify relation
1836       diff = <<~CHANGESET
1837         <osmChange>
1838         <modify>
1839           <relation id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1840         </modify>
1841         </osmChange>
1842       CHANGESET
1843
1844       # upload it
1845       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1846       assert_response :not_found, "Relation should not be found"
1847
1848       # delete node
1849       diff = <<~CHANGESET
1850         <osmChange>
1851         <delete>
1852           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1853         </delete>
1854         </osmChange>
1855       CHANGESET
1856
1857       # upload it
1858       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1859       assert_response :not_found, "Node should not be deleted"
1860
1861       # delete way
1862       diff = <<~CHANGESET
1863         <osmChange>
1864         <delete>
1865           <way id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1866         </delete>
1867         </osmChange>
1868       CHANGESET
1869
1870       # upload it
1871       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1872       assert_response :not_found, "Way should not be deleted"
1873
1874       # delete relation
1875       diff = <<~CHANGESET
1876         <osmChange>
1877         <delete>
1878           <relation id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1879         </delete>
1880         </osmChange>
1881       CHANGESET
1882
1883       # upload it
1884       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1885       assert_response :not_found, "Relation should not be deleted"
1886     end
1887
1888     def test_upload_relation_placeholder_not_fix
1889       changeset = create(:changeset)
1890
1891       auth_header = bearer_authorization_header changeset.user
1892
1893       # modify node
1894       diff = <<~CHANGESET
1895         <osmChange version='0.6'>
1896           <create>
1897             <relation id='-2' version='0' changeset='#{changeset.id}'>
1898               <member type='relation' role='' ref='-4' />
1899               <tag k='type' v='route' />
1900               <tag k='name' v='AtoB' />
1901             </relation>
1902             <relation id='-3' version='0' changeset='#{changeset.id}'>
1903               <tag k='type' v='route' />
1904               <tag k='name' v='BtoA' />
1905             </relation>
1906             <relation id='-4' version='0' changeset='#{changeset.id}'>
1907               <member type='relation' role='' ref='-2' />
1908               <member type='relation' role='' ref='-3' />
1909               <tag k='type' v='route_master' />
1910               <tag k='name' v='master' />
1911             </relation>
1912           </create>
1913         </osmChange>
1914       CHANGESET
1915
1916       # upload it
1917       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1918       assert_response :bad_request, "shouldn't be able to use reference -4 in relation -2: #{@response.body}"
1919       assert_equal "Placeholder Relation not found for reference -4 in relation -2.", @response.body
1920     end
1921
1922     def test_upload_multiple_delete_block
1923       changeset = create(:changeset)
1924
1925       auth_header = bearer_authorization_header changeset.user
1926
1927       node = create(:node)
1928       way = create(:way)
1929       create(:way_node, :way => way, :node => node)
1930       alone_node = create(:node)
1931
1932       # modify node
1933       diff = <<~CHANGESET
1934         <osmChange version='0.6'>
1935           <delete version="0.6">
1936             <node id="#{node.id}" version="#{node.version}" changeset="#{changeset.id}"/>
1937           </delete>
1938           <delete version="0.6" if-unused="true">
1939             <node id="#{alone_node.id}" version="#{alone_node.version}" changeset="#{changeset.id}"/>
1940           </delete>
1941         </osmChange>
1942       CHANGESET
1943
1944       # upload it
1945       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1946       assert_response :precondition_failed,
1947                       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
1948       assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way.id}.", @response.body
1949     end
1950
1951     ##
1952     # test initial rate limit
1953     def test_upload_initial_rate_limit
1954       # create a user
1955       user = create(:user)
1956
1957       # create some objects to use
1958       node = create(:node)
1959       way = create(:way_with_nodes, :nodes_count => 2)
1960       relation = create(:relation)
1961
1962       # create a changeset that puts us near the initial rate limit
1963       changeset = create(:changeset, :user => user,
1964                                      :created_at => Time.now.utc - 5.minutes,
1965                                      :num_changes => Settings.initial_changes_per_hour - 2)
1966
1967       # create authentication header
1968       auth_header = bearer_authorization_header user
1969
1970       # simple diff to create a node way and relation using placeholders
1971       diff = <<~CHANGESET
1972         <osmChange>
1973          <create>
1974           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1975            <tag k='foo' v='bar'/>
1976            <tag k='baz' v='bat'/>
1977           </node>
1978           <way id='-1' changeset='#{changeset.id}'>
1979            <nd ref='#{node.id}'/>
1980           </way>
1981          </create>
1982          <create>
1983           <relation id='-1' changeset='#{changeset.id}'>
1984            <member type='way' role='some' ref='#{way.id}'/>
1985            <member type='node' role='some' ref='#{node.id}'/>
1986            <member type='relation' role='some' ref='#{relation.id}'/>
1987           </relation>
1988          </create>
1989         </osmChange>
1990       CHANGESET
1991
1992       # upload it
1993       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1994       assert_response :too_many_requests, "upload did not hit rate limit"
1995     end
1996
1997     ##
1998     # test maximum rate limit
1999     def test_upload_maximum_rate_limit
2000       # create a user
2001       user = create(:user)
2002
2003       # create some objects to use
2004       node = create(:node)
2005       way = create(:way_with_nodes, :nodes_count => 2)
2006       relation = create(:relation)
2007
2008       # create a changeset to establish our initial edit time
2009       changeset = create(:changeset, :user => user,
2010                                      :created_at => Time.now.utc - 28.days)
2011
2012       # create changeset to put us near the maximum rate limit
2013       total_changes = Settings.max_changes_per_hour - 2
2014       while total_changes.positive?
2015         changes = [total_changes, Changeset::MAX_ELEMENTS].min
2016         changeset = create(:changeset, :user => user,
2017                                        :created_at => Time.now.utc - 5.minutes,
2018                                        :num_changes => changes)
2019         total_changes -= changes
2020       end
2021
2022       # create authentication header
2023       auth_header = bearer_authorization_header user
2024
2025       # simple diff to create a node way and relation using placeholders
2026       diff = <<~CHANGESET
2027         <osmChange>
2028          <create>
2029           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
2030            <tag k='foo' v='bar'/>
2031            <tag k='baz' v='bat'/>
2032           </node>
2033           <way id='-1' changeset='#{changeset.id}'>
2034            <nd ref='#{node.id}'/>
2035           </way>
2036          </create>
2037          <create>
2038           <relation id='-1' changeset='#{changeset.id}'>
2039            <member type='way' role='some' ref='#{way.id}'/>
2040            <member type='node' role='some' ref='#{node.id}'/>
2041            <member type='relation' role='some' ref='#{relation.id}'/>
2042           </relation>
2043          </create>
2044         </osmChange>
2045       CHANGESET
2046
2047       # upload it
2048       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
2049       assert_response :too_many_requests, "upload did not hit rate limit"
2050     end
2051
2052     ##
2053     # test initial size limit
2054     def test_upload_initial_size_limit
2055       # create a user
2056       user = create(:user)
2057
2058       # create a changeset that puts us near the initial size limit
2059       changeset = create(:changeset, :user => user,
2060                                      :min_lat => (-0.5 * GeoRecord::SCALE).round, :min_lon => (0.5 * GeoRecord::SCALE).round,
2061                                      :max_lat => (0.5 * GeoRecord::SCALE).round, :max_lon => (2.5 * GeoRecord::SCALE).round)
2062
2063       # create authentication header
2064       auth_header = bearer_authorization_header user
2065
2066       # simple diff to create a node
2067       diff = <<~CHANGESET
2068         <osmChange>
2069          <create>
2070           <node id='-1' lon='0.9' lat='2.9' changeset='#{changeset.id}'>
2071            <tag k='foo' v='bar'/>
2072            <tag k='baz' v='bat'/>
2073           </node>
2074          </create>
2075         </osmChange>
2076       CHANGESET
2077
2078       # upload it
2079       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
2080       assert_response :payload_too_large, "upload did not hit size limit"
2081     end
2082
2083     ##
2084     # test size limit after one week
2085     def test_upload_week_size_limit
2086       # create a user
2087       user = create(:user)
2088
2089       # create a changeset to establish our initial edit time
2090       create(:changeset, :user => user, :created_at => Time.now.utc - 7.days)
2091
2092       # create a changeset that puts us near the initial size limit
2093       changeset = create(:changeset, :user => user, :bbox => [0.5, -0.5, 2.5, 0.5])
2094
2095       # create authentication header
2096       auth_header = bearer_authorization_header user
2097
2098       # simple diff to create a node way and relation using placeholders
2099       diff = <<~CHANGESET
2100         <osmChange>
2101          <create>
2102           <node id='-1' lon='35' lat='35' changeset='#{changeset.id}'>
2103            <tag k='foo' v='bar'/>
2104            <tag k='baz' v='bat'/>
2105           </node>
2106          </create>
2107         </osmChange>
2108       CHANGESET
2109
2110       # upload it
2111       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
2112       assert_response :payload_too_large, "upload did not hit size limit"
2113     end
2114
2115     ##
2116     # when we make some simple changes we get the same changes back from the
2117     # diff download.
2118     def test_diff_download_simple
2119       node = create(:node)
2120
2121       ## First try with a non-public user, which should get a forbidden
2122       auth_header = bearer_authorization_header create(:user, :data_public => false)
2123
2124       # create a temporary changeset
2125       xml = "<osm><changeset>" \
2126             "<tag k='created_by' v='osm test suite checking changesets'/>" \
2127             "</changeset></osm>"
2128       post api_changesets_path, :params => xml, :headers => auth_header
2129       assert_response :forbidden
2130
2131       ## Now try with a normal user
2132       auth_header = bearer_authorization_header
2133
2134       # create a temporary changeset
2135       xml = "<osm><changeset>" \
2136             "<tag k='created_by' v='osm test suite checking changesets'/>" \
2137             "</changeset></osm>"
2138       post api_changesets_path, :params => xml, :headers => auth_header
2139       assert_response :success
2140       changeset_id = @response.body.to_i
2141
2142       # add a diff to it
2143       diff = <<~CHANGESET
2144         <osmChange>
2145          <modify>
2146           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset_id}' version='1'/>
2147           <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset_id}' version='2'/>
2148           <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset_id}' version='3'/>
2149           <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset_id}' version='4'/>
2150           <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset_id}' version='5'/>
2151           <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset_id}' version='6'/>
2152           <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset_id}' version='7'/>
2153           <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset_id}' version='8'/>
2154          </modify>
2155         </osmChange>
2156       CHANGESET
2157
2158       # upload it
2159       post changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
2160       assert_response :success,
2161                       "can't upload multiple versions of an element in a diff: #{@response.body}"
2162
2163       get api_changeset_download_path(changeset_id)
2164       assert_response :success
2165
2166       assert_select "osmChange", 1
2167       assert_select "osmChange>modify", 8
2168       assert_select "osmChange>modify>node", 8
2169     end
2170
2171     ##
2172     # culled this from josm to ensure that nothing in the way that josm
2173     # is formatting the request is causing it to fail.
2174     #
2175     # NOTE: the error turned out to be something else completely!
2176     def test_josm_upload
2177       auth_header = bearer_authorization_header
2178
2179       # create a temporary changeset
2180       xml = "<osm><changeset>" \
2181             "<tag k='created_by' v='osm test suite checking changesets'/>" \
2182             "</changeset></osm>"
2183       post api_changesets_path, :params => xml, :headers => auth_header
2184       assert_response :success
2185       changeset_id = @response.body.to_i
2186
2187       diff = <<~OSMFILE
2188         <osmChange version="0.6" generator="JOSM">
2189         <create version="0.6" generator="JOSM">
2190           <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
2191           <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
2192           <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
2193           <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
2194           <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
2195           <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
2196           <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
2197           <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
2198           <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
2199           <way id='-10' action='modify' visible='true' changeset='#{changeset_id}'>
2200             <nd ref='-1' />
2201             <nd ref='-2' />
2202             <nd ref='-3' />
2203             <nd ref='-4' />
2204             <nd ref='-5' />
2205             <nd ref='-6' />
2206             <nd ref='-7' />
2207             <nd ref='-8' />
2208             <nd ref='-9' />
2209             <tag k='highway' v='residential' />
2210             <tag k='name' v='Foobar Street' />
2211           </way>
2212         </create>
2213         </osmChange>
2214       OSMFILE
2215
2216       # upload it
2217       post changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
2218       assert_response :success,
2219                       "can't upload a diff from JOSM: #{@response.body}"
2220
2221       get api_changeset_download_path(changeset_id)
2222       assert_response :success
2223
2224       assert_select "osmChange", 1
2225       assert_select "osmChange>create>node", 9
2226       assert_select "osmChange>create>way", 1
2227       assert_select "osmChange>create>way>nd", 9
2228       assert_select "osmChange>create>way>tag", 2
2229     end
2230
2231     ##
2232     # when we make some complex changes we get the same changes back from the
2233     # diff download.
2234     def test_diff_download_complex
2235       node = create(:node)
2236       node2 = create(:node)
2237       way = create(:way)
2238       auth_header = bearer_authorization_header
2239
2240       # create a temporary changeset
2241       xml = "<osm><changeset>" \
2242             "<tag k='created_by' v='osm test suite checking changesets'/>" \
2243             "</changeset></osm>"
2244       post api_changesets_path, :params => xml, :headers => auth_header
2245       assert_response :success
2246       changeset_id = @response.body.to_i
2247
2248       # add a diff to it
2249       diff = <<~CHANGESET
2250         <osmChange>
2251          <delete>
2252           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset_id}' version='1'/>
2253          </delete>
2254          <create>
2255           <node id='-1' lon='0.9' lat='0.9' changeset='#{changeset_id}' version='0'/>
2256           <node id='-2' lon='0.8' lat='0.9' changeset='#{changeset_id}' version='0'/>
2257           <node id='-3' lon='0.7' lat='0.9' changeset='#{changeset_id}' version='0'/>
2258          </create>
2259          <modify>
2260           <node id='#{node2.id}' lon='2.0' lat='1.5' changeset='#{changeset_id}' version='1'/>
2261           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
2262            <nd ref='#{node2.id}'/>
2263            <nd ref='-1'/>
2264            <nd ref='-2'/>
2265            <nd ref='-3'/>
2266           </way>
2267          </modify>
2268         </osmChange>
2269       CHANGESET
2270
2271       # upload it
2272       post changeset_upload_path(changeset_id), :params => diff, :headers => auth_header
2273       assert_response :success,
2274                       "can't upload multiple versions of an element in a diff: #{@response.body}"
2275
2276       get api_changeset_download_path(changeset_id)
2277       assert_response :success
2278
2279       assert_select "osmChange", 1
2280       assert_select "osmChange>create", 3
2281       assert_select "osmChange>delete", 1
2282       assert_select "osmChange>modify", 2
2283       assert_select "osmChange>create>node", 3
2284       assert_select "osmChange>delete>node", 1
2285       assert_select "osmChange>modify>node", 1
2286       assert_select "osmChange>modify>way", 1
2287     end
2288
2289     ##
2290     # check that the bounding box of a changeset gets updated correctly
2291     # FIXME: This should really be moded to a integration test due to the with_controller
2292     def test_changeset_bbox
2293       way = create(:way)
2294       create(:way_node, :way => way, :node => create(:node, :lat => 0.3, :lon => 0.3))
2295
2296       auth_header = bearer_authorization_header
2297
2298       # create a new changeset
2299       xml = "<osm><changeset/></osm>"
2300       post api_changesets_path, :params => xml, :headers => auth_header
2301       assert_response :success, "Creating of changeset failed."
2302       changeset_id = @response.body.to_i
2303
2304       # add a single node to it
2305       with_controller(NodesController.new) do
2306         xml = "<osm><node lon='0.1' lat='0.2' changeset='#{changeset_id}'/></osm>"
2307         post api_nodes_path, :params => xml, :headers => auth_header
2308         assert_response :success, "Couldn't create node."
2309       end
2310
2311       # get the bounding box back from the changeset
2312       get api_changeset_path(changeset_id)
2313       assert_response :success, "Couldn't read back changeset."
2314       assert_select "osm>changeset[min_lon='0.1000000']", 1
2315       assert_select "osm>changeset[max_lon='0.1000000']", 1
2316       assert_select "osm>changeset[min_lat='0.2000000']", 1
2317       assert_select "osm>changeset[max_lat='0.2000000']", 1
2318
2319       # add another node to it
2320       with_controller(NodesController.new) do
2321         xml = "<osm><node lon='0.2' lat='0.1' changeset='#{changeset_id}'/></osm>"
2322         post api_nodes_path, :params => xml, :headers => auth_header
2323         assert_response :success, "Couldn't create second node."
2324       end
2325
2326       # get the bounding box back from the changeset
2327       get api_changeset_path(changeset_id)
2328       assert_response :success, "Couldn't read back changeset for the second time."
2329       assert_select "osm>changeset[min_lon='0.1000000']", 1
2330       assert_select "osm>changeset[max_lon='0.2000000']", 1
2331       assert_select "osm>changeset[min_lat='0.1000000']", 1
2332       assert_select "osm>changeset[max_lat='0.2000000']", 1
2333
2334       # add (delete) a way to it, which contains a point at (3,3)
2335       with_controller(WaysController.new) do
2336         xml = update_changeset(xml_for_way(way), changeset_id)
2337         delete api_way_path(way), :params => xml.to_s, :headers => auth_header
2338         assert_response :success, "Couldn't delete a way."
2339       end
2340
2341       # get the bounding box back from the changeset
2342       get api_changeset_path(changeset_id)
2343       assert_response :success, "Couldn't read back changeset for the third time."
2344       assert_select "osm>changeset[min_lon='0.1000000']", 1
2345       assert_select "osm>changeset[max_lon='0.3000000']", 1
2346       assert_select "osm>changeset[min_lat='0.1000000']", 1
2347       assert_select "osm>changeset[max_lat='0.3000000']", 1
2348     end
2349
2350     ##
2351     # check updating tags on a changeset
2352     def test_changeset_update
2353       private_user = create(:user, :data_public => false)
2354       private_changeset = create(:changeset, :user => private_user)
2355       user = create(:user)
2356       changeset = create(:changeset, :user => user)
2357
2358       ## First try with a non-public user
2359       new_changeset = create_changeset_xml(:user => private_user)
2360       new_tag = XML::Node.new "tag"
2361       new_tag["k"] = "tagtesting"
2362       new_tag["v"] = "valuetesting"
2363       new_changeset.find("//osm/changeset").first << new_tag
2364
2365       # try without any authorization
2366       put api_changeset_path(private_changeset), :params => new_changeset.to_s
2367       assert_response :unauthorized
2368
2369       # try with the wrong authorization
2370       auth_header = bearer_authorization_header
2371       put api_changeset_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2372       assert_response :conflict
2373
2374       # now this should get an unauthorized
2375       auth_header = bearer_authorization_header private_user
2376       put api_changeset_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2377       assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
2378
2379       ## Now try with the public user
2380       new_changeset = create_changeset_xml(:id => 1)
2381       new_tag = XML::Node.new "tag"
2382       new_tag["k"] = "tagtesting"
2383       new_tag["v"] = "valuetesting"
2384       new_changeset.find("//osm/changeset").first << new_tag
2385
2386       # try without any authorization
2387       put api_changeset_path(changeset), :params => new_changeset.to_s
2388       assert_response :unauthorized
2389
2390       # try with the wrong authorization
2391       auth_header = bearer_authorization_header
2392       put api_changeset_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2393       assert_response :conflict
2394
2395       # now this should work...
2396       auth_header = bearer_authorization_header user
2397       put api_changeset_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2398       assert_response :success
2399
2400       assert_select "osm>changeset[id='#{changeset.id}']", 1
2401       assert_select "osm>changeset>tag", 1
2402       assert_select "osm>changeset>tag[k='tagtesting'][v='valuetesting']", 1
2403     end
2404
2405     ##
2406     # check that a user different from the one who opened the changeset
2407     # can't modify it.
2408     def test_changeset_update_invalid
2409       auth_header = bearer_authorization_header
2410
2411       changeset = create(:changeset)
2412       new_changeset = create_changeset_xml(:user => changeset.user, :id => changeset.id)
2413       new_tag = XML::Node.new "tag"
2414       new_tag["k"] = "testing"
2415       new_tag["v"] = "testing"
2416       new_changeset.find("//osm/changeset").first << new_tag
2417
2418       put api_changeset_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2419       assert_response :conflict
2420     end
2421
2422     ##
2423     # check that a changeset can contain a certain max number of changes.
2424     ## FIXME should be changed to an integration test due to the with_controller
2425     def test_changeset_limits
2426       user = create(:user)
2427       auth_header = bearer_authorization_header user
2428
2429       # create an old changeset to ensure we have the maximum rate limit
2430       create(:changeset, :user => user, :created_at => Time.now.utc - 28.days)
2431
2432       # open a new changeset
2433       xml = "<osm><changeset/></osm>"
2434       post api_changesets_path, :params => xml, :headers => auth_header
2435       assert_response :success, "can't create a new changeset"
2436       cs_id = @response.body.to_i
2437
2438       # start the counter just short of where the changeset should finish.
2439       offset = 10
2440       # alter the database to set the counter on the changeset directly,
2441       # otherwise it takes about 6 minutes to fill all of them.
2442       changeset = Changeset.find(cs_id)
2443       changeset.num_changes = Changeset::MAX_ELEMENTS - offset
2444       changeset.save!
2445
2446       with_controller(NodesController.new) do
2447         # create a new node
2448         xml = "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
2449         post api_nodes_path, :params => xml, :headers => auth_header
2450         assert_response :success, "can't create a new node"
2451         node_id = @response.body.to_i
2452
2453         get api_node_path(node_id)
2454         assert_response :success, "can't read back new node"
2455         node_doc = XML::Parser.string(@response.body).parse
2456         node_xml = node_doc.find("//osm/node").first
2457
2458         # loop until we fill the changeset with nodes
2459         offset.times do |i|
2460           node_xml["lat"] = rand.to_s
2461           node_xml["lon"] = rand.to_s
2462           node_xml["version"] = (i + 1).to_s
2463
2464           put api_node_path(node_id), :params => node_doc.to_s, :headers => auth_header
2465           assert_response :success, "attempt #{i} should have succeeded"
2466         end
2467
2468         # trying again should fail
2469         node_xml["lat"] = rand.to_s
2470         node_xml["lon"] = rand.to_s
2471         node_xml["version"] = offset.to_s
2472
2473         put api_node_path(node_id), :params => node_doc.to_s, :headers => auth_header
2474         assert_response :conflict, "final attempt should have failed"
2475       end
2476
2477       changeset = Changeset.find(cs_id)
2478       assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
2479
2480       # check that the changeset is now closed as well
2481       assert_not(changeset.open?,
2482                  "changeset should have been auto-closed by exceeding " \
2483                  "element limit.")
2484     end
2485
2486     private
2487
2488     ##
2489     # check that the output consists of one specific changeset
2490     def assert_single_changeset(changeset, &)
2491       assert_dom "> changeset", 1 do
2492         assert_dom "> @id", changeset.id.to_s
2493         assert_dom "> @created_at", changeset.created_at.xmlschema
2494         if changeset.open?
2495           assert_dom "> @open", "true"
2496           assert_dom "> @closed_at", 0
2497         else
2498           assert_dom "> @open", "false"
2499           assert_dom "> @closed_at", changeset.closed_at.xmlschema
2500         end
2501         assert_dom "> @comments_count", changeset.comments.length.to_s
2502         assert_dom "> @changes_count", changeset.num_changes.to_s
2503         yield if block_given?
2504       end
2505     end
2506
2507     def assert_single_changeset_json(changeset, js)
2508       assert_equal changeset.id, js["changeset"]["id"]
2509       assert_equal changeset.created_at.xmlschema, js["changeset"]["created_at"]
2510       if changeset.open?
2511         assert js["changeset"]["open"]
2512         assert_nil js["changeset"]["closed_at"]
2513       else
2514         assert_not js["changeset"]["open"]
2515         assert_equal changeset.closed_at.xmlschema, js["changeset"]["closed_at"]
2516       end
2517       assert_equal changeset.comments.length, js["changeset"]["comments_count"]
2518       assert_equal changeset.num_changes, js["changeset"]["changes_count"]
2519     end
2520
2521     ##
2522     # check that certain changesets exist in the output in the specified order
2523     def assert_changesets_in_order(changesets)
2524       assert_select "osm>changeset", changesets.size
2525       changesets.each_with_index do |changeset, index|
2526         assert_select "osm>changeset:nth-child(#{index + 1})[id='#{changeset.id}']", 1
2527       end
2528     end
2529
2530     ##
2531     # update the changeset_id of a way element
2532     def update_changeset(xml, changeset_id)
2533       xml_attr_rewrite(xml, "changeset", changeset_id)
2534     end
2535
2536     ##
2537     # update an attribute in a way element
2538     def xml_attr_rewrite(xml, name, value)
2539       xml.find("//osm/way").first[name] = value.to_s
2540       xml
2541     end
2542
2543     ##
2544     # build XML for changesets
2545     def create_changeset_xml(user: nil, id: nil)
2546       root = XML::Document.new
2547       root.root = XML::Node.new "osm"
2548       cs = XML::Node.new "changeset"
2549       if user
2550         cs["user"] = user.display_name
2551         cs["uid"] = user.id.to_s
2552       end
2553       cs["id"] = id.to_s if id
2554       root.root << cs
2555       root
2556     end
2557   end
2558 end