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