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