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