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