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