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