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