]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets_controller_test.rb
7bb768b0da8fdc7efe22c1321d114ef4c834d2b5
[rails.git] / test / controllers / api / changesets_controller_test.rb
1 require "test_helper"
2
3 module Api
4   class ChangesetsControllerTest < ActionDispatch::IntegrationTest
5     ##
6     # test all routes which lead to this controller
7     def test_routes
8       assert_routing(
9         { :path => "/api/0.6/changeset/create", :method => :put },
10         { :controller => "api/changesets", :action => "create" }
11       )
12       assert_routing(
13         { :path => "/api/0.6/changeset/1/upload", :method => :post },
14         { :controller => "api/changesets", :action => "upload", :id => "1" }
15       )
16       assert_routing(
17         { :path => "/api/0.6/changeset/1/download", :method => :get },
18         { :controller => "api/changesets", :action => "download", :id => "1" }
19       )
20       assert_routing(
21         { :path => "/api/0.6/changeset/1", :method => :get },
22         { :controller => "api/changesets", :action => "show", :id => "1" }
23       )
24       assert_routing(
25         { :path => "/api/0.6/changeset/1.json", :method => :get },
26         { :controller => "api/changesets", :action => "show", :id => "1", :format => "json" }
27       )
28       assert_routing(
29         { :path => "/api/0.6/changeset/1/subscribe", :method => :post },
30         { :controller => "api/changesets", :action => "subscribe", :id => "1" }
31       )
32       assert_routing(
33         { :path => "/api/0.6/changeset/1/subscribe.json", :method => :post },
34         { :controller => "api/changesets", :action => "subscribe", :id => "1", :format => "json" }
35       )
36       assert_routing(
37         { :path => "/api/0.6/changeset/1/unsubscribe", :method => :post },
38         { :controller => "api/changesets", :action => "unsubscribe", :id => "1" }
39       )
40       assert_routing(
41         { :path => "/api/0.6/changeset/1/unsubscribe.json", :method => :post },
42         { :controller => "api/changesets", :action => "unsubscribe", :id => "1", :format => "json" }
43       )
44       assert_routing(
45         { :path => "/api/0.6/changeset/1", :method => :put },
46         { :controller => "api/changesets", :action => "update", :id => "1" }
47       )
48       assert_routing(
49         { :path => "/api/0.6/changeset/1/close", :method => :put },
50         { :controller => "api/changesets", :action => "close", :id => "1" }
51       )
52       assert_routing(
53         { :path => "/api/0.6/changesets", :method => :get },
54         { :controller => "api/changesets", :action => "index" }
55       )
56       assert_routing(
57         { :path => "/api/0.6/changesets.json", :method => :get },
58         { :controller => "api/changesets", :action => "index", :format => "json" }
59       )
60     end
61
62     # -----------------------
63     # Test simple changeset creation
64     # -----------------------
65
66     def test_create
67       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
68       # Create the first user's changeset
69       xml = "<osm><changeset>" \
70             "<tag k='created_by' v='osm test suite checking changesets'/>" \
71             "</changeset></osm>"
72       put changeset_create_path, :params => xml, :headers => auth_header
73       assert_require_public_data
74
75       auth_header = basic_authorization_header create(:user).email, "test"
76       # Create the first user's changeset
77       xml = "<osm><changeset>" \
78             "<tag k='created_by' v='osm test suite checking changesets'/>" \
79             "</changeset></osm>"
80       put changeset_create_path, :params => xml, :headers => auth_header
81
82       assert_response :success, "Creation of changeset did not return success status"
83       newid = @response.body.to_i
84
85       # check end time, should be an hour ahead of creation time
86       cs = Changeset.find(newid)
87       duration = cs.closed_at - cs.created_at
88       # the difference can either be a rational, or a floating point number
89       # of seconds, depending on the code path taken :-(
90       if duration.instance_of?(Rational)
91         assert_equal Rational(1, 24), duration, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
92       else
93         # must be number of seconds...
94         assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
95       end
96
97       # checks if uploader was subscribed
98       assert_equal 1, cs.subscribers.length
99     end
100
101     def test_create_invalid
102       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
103       xml = "<osm><changeset></osm>"
104       put changeset_create_path, :params => xml, :headers => auth_header
105       assert_require_public_data
106
107       ## Try the public user
108       auth_header = basic_authorization_header create(:user).email, "test"
109       xml = "<osm><changeset></osm>"
110       put changeset_create_path, :params => xml, :headers => auth_header
111       assert_response :bad_request, "creating a invalid changeset should fail"
112     end
113
114     def test_create_invalid_no_content
115       ## First check with no auth
116       put changeset_create_path
117       assert_response :unauthorized, "shouldn't be able to create a changeset with no auth"
118
119       ## Now try to with a non-public user
120       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
121       put changeset_create_path, :headers => auth_header
122       assert_require_public_data
123
124       ## Try an inactive user
125       auth_header = basic_authorization_header create(:user, :pending).email, "test"
126       put changeset_create_path, :headers => auth_header
127       assert_inactive_user
128
129       ## Now try to use a normal user
130       auth_header = basic_authorization_header create(:user).email, "test"
131       put changeset_create_path, :headers => auth_header
132       assert_response :bad_request, "creating a changeset with no content should fail"
133     end
134
135     def test_create_wrong_method
136       auth_header = basic_authorization_header create(:user).email, "test"
137
138       get changeset_create_path, :headers => auth_header
139       assert_response :not_found
140       assert_template "rescues/routing_error"
141
142       post changeset_create_path, :headers => auth_header
143       assert_response :not_found
144       assert_template "rescues/routing_error"
145     end
146
147     ##
148     # check that the changeset can be shown and returns the correct
149     # document structure.
150     def test_show
151       changeset = create(:changeset)
152
153       get changeset_show_path(changeset)
154       assert_response :success, "cannot get first changeset"
155
156       assert_select "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
157       assert_single_changeset changeset
158       assert_select "osm>changeset>discussion", 0
159
160       get changeset_show_path(changeset), :params => { :include_discussion => true }
161       assert_response :success, "cannot get first changeset with comments"
162
163       assert_select "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
164       assert_single_changeset changeset
165       assert_select "osm>changeset>discussion", 1
166       assert_select "osm>changeset>discussion>comment", 0
167     end
168
169     def test_show_comments
170       # all comments visible
171       changeset = create(:changeset, :closed)
172       comment1, comment2, comment3 = create_list(:changeset_comment, 3, :changeset_id => changeset.id)
173
174       get changeset_show_path(changeset), :params => { :include_discussion => true }
175       assert_response :success, "cannot get closed changeset with comments"
176
177       assert_select "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
178       assert_single_changeset changeset
179       assert_select "osm>changeset>discussion", 1
180       assert_select "osm>changeset>discussion>comment", 3
181       assert_select "osm>changeset>discussion>comment:nth-child(1)>@id", comment1.id.to_s
182       assert_select "osm>changeset>discussion>comment:nth-child(1)>@visible", "true"
183       assert_select "osm>changeset>discussion>comment:nth-child(2)>@id", comment2.id.to_s
184       assert_select "osm>changeset>discussion>comment:nth-child(2)>@visible", "true"
185       assert_select "osm>changeset>discussion>comment:nth-child(3)>@id", comment3.id.to_s
186       assert_select "osm>changeset>discussion>comment:nth-child(3)>@visible", "true"
187
188       # one hidden comment not included because not asked for
189       comment2.update(:visible => false)
190
191       get changeset_show_path(changeset), :params => { :include_discussion => true }
192       assert_response :success, "cannot get closed changeset with comments"
193
194       assert_select "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
195       assert_single_changeset changeset
196       assert_select "osm>changeset>discussion", 1
197       assert_select "osm>changeset>discussion>comment", 2
198       assert_select "osm>changeset>discussion>comment:nth-child(1)>@id", comment1.id.to_s
199       assert_select "osm>changeset>discussion>comment:nth-child(1)>@visible", "true"
200       assert_select "osm>changeset>discussion>comment:nth-child(2)>@id", comment3.id.to_s
201       assert_select "osm>changeset>discussion>comment:nth-child(2)>@visible", "true"
202
203       # one hidden comment not included because no permissions
204       get changeset_show_path(changeset), :params => { :include_discussion => true, :show_hidden_comments => true }
205       assert_response :success, "cannot get closed changeset with comments"
206
207       assert_select "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
208       assert_single_changeset changeset
209       assert_select "osm>changeset>discussion", 1
210       assert_select "osm>changeset>discussion>comment", 2
211       assert_select "osm>changeset>discussion>comment:nth-child(1)>@id", comment1.id.to_s
212       assert_select "osm>changeset>discussion>comment:nth-child(1)>@visible", "true"
213       # maybe will show an empty comment element with visible=false in the future
214       assert_select "osm>changeset>discussion>comment:nth-child(2)>@id", comment3.id.to_s
215       assert_select "osm>changeset>discussion>comment:nth-child(2)>@visible", "true"
216
217       # one hidden comment shown to moderators
218       moderator_user = create(:moderator_user)
219       auth_header = basic_authorization_header moderator_user.email, "test"
220       get changeset_show_path(changeset), :params => { :include_discussion => true, :show_hidden_comments => true },
221                                           :headers => auth_header
222       assert_response :success, "cannot get closed changeset with comments"
223
224       assert_select "osm[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
225       assert_single_changeset changeset
226       assert_select "osm>changeset>discussion", 1
227       assert_select "osm>changeset>discussion>comment", 3
228       assert_select "osm>changeset>discussion>comment:nth-child(1)>@id", comment1.id.to_s
229       assert_select "osm>changeset>discussion>comment:nth-child(1)>@visible", "true"
230       assert_select "osm>changeset>discussion>comment:nth-child(2)>@id", comment2.id.to_s
231       assert_select "osm>changeset>discussion>comment:nth-child(2)>@visible", "false"
232       assert_select "osm>changeset>discussion>comment:nth-child(3)>@id", comment3.id.to_s
233       assert_select "osm>changeset>discussion>comment:nth-child(3)>@visible", "true"
234     end
235
236     def test_show_json
237       changeset = create(:changeset)
238
239       get changeset_show_path(changeset), :params => { :format => "json" }
240       assert_response :success, "cannot get first changeset"
241
242       js = ActiveSupport::JSON.decode(@response.body)
243       assert_not_nil js
244
245       assert_equal Settings.api_version, js["version"]
246       assert_equal Settings.generator, js["generator"]
247       assert_single_changeset_json changeset, js
248       assert_nil js["changeset"]["tags"]
249       assert_nil js["changeset"]["comments"]
250       assert_equal changeset.user.id, js["changeset"]["uid"]
251       assert_equal changeset.user.display_name, js["changeset"]["user"]
252
253       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true }
254       assert_response :success, "cannot get first changeset with comments"
255
256       js = ActiveSupport::JSON.decode(@response.body)
257       assert_not_nil js
258       assert_equal Settings.api_version, js["version"]
259       assert_equal Settings.generator, js["generator"]
260       assert_single_changeset_json changeset, js
261       assert_nil js["changeset"]["tags"]
262       assert_nil js["changeset"]["min_lat"]
263       assert_nil js["changeset"]["min_lon"]
264       assert_nil js["changeset"]["max_lat"]
265       assert_nil js["changeset"]["max_lon"]
266       assert_equal 0, js["changeset"]["comments"].count
267     end
268
269     def test_show_comments_json
270       # all comments visible
271       changeset = create(:changeset, :closed)
272       comment0, comment1, comment2 = create_list(:changeset_comment, 3, :changeset_id => changeset.id)
273
274       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true }
275       assert_response :success, "cannot get closed changeset with comments"
276
277       js = ActiveSupport::JSON.decode(@response.body)
278       assert_not_nil js
279       assert_equal Settings.api_version, js["version"]
280       assert_equal Settings.generator, js["generator"]
281       assert_single_changeset_json changeset, js
282       assert_equal 3, js["changeset"]["comments"].count
283       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
284       assert js["changeset"]["comments"][0]["visible"]
285       assert_equal comment1.id, js["changeset"]["comments"][1]["id"]
286       assert js["changeset"]["comments"][1]["visible"]
287       assert_equal comment2.id, js["changeset"]["comments"][2]["id"]
288       assert js["changeset"]["comments"][2]["visible"]
289
290       # one hidden comment not included because not asked for
291       comment1.update(:visible => false)
292
293       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true }
294       assert_response :success, "cannot get closed changeset with comments"
295
296       js = ActiveSupport::JSON.decode(@response.body)
297       assert_not_nil js
298       assert_equal Settings.api_version, js["version"]
299       assert_equal Settings.generator, js["generator"]
300       assert_single_changeset_json changeset, js
301       assert_equal 2, js["changeset"]["comments"].count
302       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
303       assert js["changeset"]["comments"][0]["visible"]
304       assert_equal comment2.id, js["changeset"]["comments"][1]["id"]
305       assert js["changeset"]["comments"][1]["visible"]
306
307       # one hidden comment not included because no permissions
308       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true, :show_hidden_comments => true }
309       assert_response :success, "cannot get closed changeset with comments"
310
311       js = ActiveSupport::JSON.decode(@response.body)
312       assert_not_nil js
313       assert_equal Settings.api_version, js["version"]
314       assert_equal Settings.generator, js["generator"]
315       assert_single_changeset_json changeset, js
316       assert_equal 2, js["changeset"]["comments"].count
317       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
318       assert js["changeset"]["comments"][0]["visible"]
319       # maybe will show an empty comment element with visible=false in the future
320       assert_equal comment2.id, js["changeset"]["comments"][1]["id"]
321       assert js["changeset"]["comments"][1]["visible"]
322
323       # one hidden comment shown to moderators
324       moderator_user = create(:moderator_user)
325       auth_header = basic_authorization_header moderator_user.email, "test"
326       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true, :show_hidden_comments => true },
327                                           :headers => auth_header
328       assert_response :success, "cannot get closed changeset with comments"
329
330       js = ActiveSupport::JSON.decode(@response.body)
331       assert_not_nil js
332       assert_equal Settings.api_version, js["version"]
333       assert_equal Settings.generator, js["generator"]
334       assert_single_changeset_json changeset, js
335       assert_equal 3, js["changeset"]["comments"].count
336       assert_equal comment0.id, js["changeset"]["comments"][0]["id"]
337       assert js["changeset"]["comments"][0]["visible"]
338       assert_equal comment1.id, js["changeset"]["comments"][1]["id"]
339       assert_not js["changeset"]["comments"][1]["visible"]
340       assert_equal comment2.id, js["changeset"]["comments"][2]["id"]
341       assert js["changeset"]["comments"][2]["visible"]
342     end
343
344     def test_show_tag_and_discussion_json
345       changeset = create(:changeset, :closed)
346
347       tag1 = ChangesetTag.new
348       tag1.changeset_id = changeset.id
349       tag1.k = "created_by"
350       tag1.v = "JOSM/1.5 (18364)"
351
352       tag2 = ChangesetTag.new
353       tag2.changeset_id = changeset.id
354       tag2.k = "comment"
355       tag2.v = "changeset comment"
356
357       changeset.changeset_tags = [tag1, tag2]
358
359       create_list(:changeset_comment, 3, :changeset_id => changeset.id)
360
361       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true }
362       assert_response :success, "cannot get closed changeset with comments"
363
364       js = ActiveSupport::JSON.decode(@response.body)
365
366       assert_not_nil js
367       assert_equal Settings.api_version, js["version"]
368       assert_equal Settings.generator, js["generator"]
369       assert_single_changeset_json changeset, js
370       assert_equal 2, js["changeset"]["tags"].count
371       assert_equal 3, js["changeset"]["comments"].count
372       assert_equal 3, js["changeset"]["comments_count"]
373       assert_equal 0, js["changeset"]["changes_count"]
374       assert_not_nil js["changeset"]["comments"][0]["uid"]
375       assert_not_nil js["changeset"]["comments"][0]["user"]
376       assert_not_nil js["changeset"]["comments"][0]["text"]
377     end
378
379     def test_show_bbox_json
380       # test bbox attribute
381       changeset = create(:changeset, :min_lat => (-5 * GeoRecord::SCALE).round, :min_lon => (5 * GeoRecord::SCALE).round,
382                                      :max_lat => (15 * GeoRecord::SCALE).round, :max_lon => (12 * GeoRecord::SCALE).round)
383
384       get changeset_show_path(changeset), :params => { :format => "json" }
385       assert_response :success, "cannot get first changeset"
386
387       js = ActiveSupport::JSON.decode(@response.body)
388       assert_not_nil js
389       assert_equal(-5, js["changeset"]["min_lat"])
390       assert_equal 5, js["changeset"]["min_lon"]
391       assert_equal 15, js["changeset"]["max_lat"]
392       assert_equal 12, js["changeset"]["max_lon"]
393     end
394
395     ##
396     # check that a changeset that doesn't exist returns an appropriate message
397     def test_show_not_found
398       [0, -32, 233455644, "afg", "213"].each do |id|
399         get changeset_show_path(:id => id)
400         assert_response :not_found, "should get a not found"
401       rescue ActionController::UrlGenerationError => e
402         assert_match(/No route matches/, e.to_s)
403       end
404     end
405
406     ##
407     # test that the user who opened a change can close it
408     def test_close
409       private_user = create(:user, :data_public => false)
410       private_changeset = create(:changeset, :user => private_user)
411       user = create(:user)
412       changeset = create(:changeset, :user => user)
413
414       ## Try without authentication
415       put changeset_close_path(changeset)
416       assert_response :unauthorized
417
418       ## Try using the non-public user
419       auth_header = basic_authorization_header private_user.email, "test"
420       put changeset_close_path(private_changeset), :headers => auth_header
421       assert_require_public_data
422
423       ## The try with the public user
424       auth_header = basic_authorization_header user.email, "test"
425
426       cs_id = changeset.id
427       put changeset_close_path(:id => cs_id), :headers => auth_header
428       assert_response :success
429
430       # test that it really is closed now
431       cs = Changeset.find(changeset.id)
432       assert_not(cs.open?,
433                  "changeset should be closed now (#{cs.closed_at} > #{Time.now.utc}.")
434     end
435
436     ##
437     # test that a different user can't close another user's changeset
438     def test_close_invalid
439       user = create(:user)
440       changeset = create(:changeset)
441
442       auth_header = basic_authorization_header user.email, "test"
443
444       put changeset_close_path(changeset), :headers => auth_header
445       assert_response :conflict
446       assert_equal "The user doesn't own that changeset", @response.body
447     end
448
449     ##
450     # test that you can't close using another method
451     def test_close_method_invalid
452       user = create(:user)
453       changeset = create(:changeset, :user => user)
454
455       auth_header = basic_authorization_header user.email, "test"
456
457       get changeset_close_path(changeset), :headers => auth_header
458       assert_response :not_found
459       assert_template "rescues/routing_error"
460
461       post changeset_close_path(changeset), :headers => auth_header
462       assert_response :not_found
463       assert_template "rescues/routing_error"
464     end
465
466     ##
467     # check that you can't close a changeset that isn't found
468     def test_close_not_found
469       cs_ids = [0, -132, "123"]
470
471       # First try to do it with no auth
472       cs_ids.each do |id|
473         put changeset_close_path(:id => id)
474         assert_response :unauthorized, "Shouldn't be able close the non-existant changeset #{id}, when not authorized"
475       rescue ActionController::UrlGenerationError => e
476         assert_match(/No route matches/, e.to_s)
477       end
478
479       # Now try with auth
480       auth_header = basic_authorization_header create(:user).email, "test"
481       cs_ids.each do |id|
482         put changeset_close_path(:id => id), :headers => auth_header
483         assert_response :not_found, "The changeset #{id} doesn't exist, so can't be closed"
484       rescue ActionController::UrlGenerationError => e
485         assert_match(/No route matches/, e.to_s)
486       end
487     end
488
489     ##
490     # upload something simple, but valid and check that it can
491     # be read back ok
492     # Also try without auth and another user.
493     def test_upload_simple_valid
494       private_user = create(:user, :data_public => false)
495       private_changeset = create(:changeset, :user => private_user)
496       user = create(:user)
497       changeset = create(:changeset, :user => user)
498
499       node = create(:node)
500       way = create(:way)
501       relation = create(:relation)
502       other_relation = create(:relation)
503       # create some tags, since we test that they are removed later
504       create(:node_tag, :node => node)
505       create(:way_tag, :way => way)
506       create(:relation_tag, :relation => relation)
507
508       ## Try with no auth
509       changeset_id = changeset.id
510
511       # simple diff to change a node, way and relation by removing
512       # their tags
513       diff = <<~CHANGESET
514         <osmChange>
515          <modify>
516           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
517           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
518            <nd ref='#{node.id}'/>
519           </way>
520          </modify>
521          <modify>
522           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
523            <member type='way' role='some' ref='#{way.id}'/>
524            <member type='node' role='some' ref='#{node.id}'/>
525            <member type='relation' role='some' ref='#{other_relation.id}'/>
526           </relation>
527          </modify>
528         </osmChange>
529       CHANGESET
530
531       # upload it
532       post changeset_upload_path(changeset), :params => diff
533       assert_response :unauthorized,
534                       "shouldn't be able to upload a simple valid diff to changeset: #{@response.body}"
535
536       ## Now try with a private user
537       auth_header = basic_authorization_header private_user.email, "test"
538       changeset_id = private_changeset.id
539
540       # simple diff to change a node, way and relation by removing
541       # their tags
542       diff = <<~CHANGESET
543         <osmChange>
544          <modify>
545           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
546           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
547            <nd ref='#{node.id}'/>
548           </way>
549          </modify>
550          <modify>
551           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
552            <member type='way' role='some' ref='#{way.id}'/>
553            <member type='node' role='some' ref='#{node.id}'/>
554            <member type='relation' role='some' ref='#{other_relation.id}'/>
555           </relation>
556          </modify>
557         </osmChange>
558       CHANGESET
559
560       # upload it
561       post changeset_upload_path(private_changeset), :params => diff, :headers => auth_header
562       assert_response :forbidden,
563                       "can't upload a simple valid diff to changeset: #{@response.body}"
564
565       ## Now try with the public user
566       auth_header = basic_authorization_header user.email, "test"
567       changeset_id = changeset.id
568
569       # simple diff to change a node, way and relation by removing
570       # their tags
571       diff = <<~CHANGESET
572         <osmChange>
573          <modify>
574           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
575           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
576            <nd ref='#{node.id}'/>
577           </way>
578          </modify>
579          <modify>
580           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
581            <member type='way' role='some' ref='#{way.id}'/>
582            <member type='node' role='some' ref='#{node.id}'/>
583            <member type='relation' role='some' ref='#{other_relation.id}'/>
584           </relation>
585          </modify>
586         </osmChange>
587       CHANGESET
588
589       # upload it
590       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
591       assert_response :success,
592                       "can't upload a simple valid diff to changeset: #{@response.body}"
593
594       # check that the changes made it into the database
595       assert_equal 0, Node.find(node.id).tags.size, "node #{node.id} should now have no tags"
596       assert_equal 0, Way.find(way.id).tags.size, "way #{way.id} should now have no tags"
597       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
598     end
599
600     ##
601     # upload something which creates new objects using placeholders
602     def test_upload_create_valid
603       user = create(:user)
604       changeset = create(:changeset, :user => user)
605       node = create(:node)
606       way = create(:way_with_nodes, :nodes_count => 2)
607       relation = create(:relation)
608
609       auth_header = basic_authorization_header user.email, "test"
610
611       # simple diff to create a node way and relation using placeholders
612       diff = <<~CHANGESET
613         <osmChange>
614          <create>
615           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
616            <tag k='foo' v='bar'/>
617            <tag k='baz' v='bat'/>
618           </node>
619           <way id='-1' changeset='#{changeset.id}'>
620            <nd ref='#{node.id}'/>
621           </way>
622          </create>
623          <create>
624           <relation id='-1' changeset='#{changeset.id}'>
625            <member type='way' role='some' ref='#{way.id}'/>
626            <member type='node' role='some' ref='#{node.id}'/>
627            <member type='relation' role='some' ref='#{relation.id}'/>
628           </relation>
629          </create>
630         </osmChange>
631       CHANGESET
632
633       # upload it
634       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
635       assert_response :success,
636                       "can't upload a simple valid creation to changeset: #{@response.body}"
637
638       # check the returned payload
639       assert_select "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
640       assert_select "diffResult>node", 1
641       assert_select "diffResult>way", 1
642       assert_select "diffResult>relation", 1
643
644       # inspect the response to find out what the new element IDs are
645       doc = XML::Parser.string(@response.body).parse
646       new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
647       new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
648       new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
649
650       # check the old IDs are all present and negative one
651       assert_equal(-1, doc.find("//diffResult/node").first["old_id"].to_i)
652       assert_equal(-1, doc.find("//diffResult/way").first["old_id"].to_i)
653       assert_equal(-1, doc.find("//diffResult/relation").first["old_id"].to_i)
654
655       # check the versions are present and equal one
656       assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
657       assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
658       assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
659
660       # check that the changes made it into the database
661       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
662       assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
663       assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
664     end
665
666     ##
667     # test a complex delete where we delete elements which rely on eachother
668     # in the same transaction.
669     def test_upload_delete
670       changeset = create(:changeset)
671       super_relation = create(:relation)
672       used_relation = create(:relation)
673       used_way = create(:way)
674       used_node = create(:node)
675       create(:relation_member, :relation => super_relation, :member => used_relation)
676       create(:relation_member, :relation => super_relation, :member => used_way)
677       create(:relation_member, :relation => super_relation, :member => used_node)
678
679       auth_header = basic_authorization_header changeset.user.display_name, "test"
680
681       diff = XML::Document.new
682       diff.root = XML::Node.new "osmChange"
683       delete = XML::Node.new "delete"
684       diff.root << delete
685       delete << xml_node_for_relation(super_relation)
686       delete << xml_node_for_relation(used_relation)
687       delete << xml_node_for_way(used_way)
688       delete << xml_node_for_node(used_node)
689
690       # update the changeset to one that this user owns
691       %w[node way relation].each do |type|
692         delete.find("//osmChange/delete/#{type}").each do |n|
693           n["changeset"] = changeset.id.to_s
694         end
695       end
696
697       # upload it
698       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
699       assert_response :success,
700                       "can't upload a deletion diff to changeset: #{@response.body}"
701
702       # check the response is well-formed
703       assert_select "diffResult>node", 1
704       assert_select "diffResult>way", 1
705       assert_select "diffResult>relation", 2
706
707       # check that everything was deleted
708       assert_not Node.find(used_node.id).visible
709       assert_not Way.find(used_way.id).visible
710       assert_not Relation.find(super_relation.id).visible
711       assert_not Relation.find(used_relation.id).visible
712     end
713
714     ##
715     # test uploading a delete with no lat/lon, as they are optional in
716     # the osmChange spec.
717     def test_upload_nolatlon_delete
718       node = create(:node)
719       changeset = create(:changeset)
720
721       auth_header = basic_authorization_header changeset.user.display_name, "test"
722       diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
723
724       # upload it
725       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
726       assert_response :success,
727                       "can't upload a deletion diff to changeset: #{@response.body}"
728
729       # check the response is well-formed
730       assert_select "diffResult>node", 1
731
732       # check that everything was deleted
733       assert_not Node.find(node.id).visible
734     end
735
736     def test_repeated_changeset_create
737       3.times do
738         auth_header = basic_authorization_header create(:user).email, "test"
739
740         # create a temporary changeset
741         xml = "<osm><changeset>" \
742               "<tag k='created_by' v='osm test suite checking changesets'/>" \
743               "</changeset></osm>"
744         assert_difference "Changeset.count", 1 do
745           put changeset_create_path, :params => xml, :headers => auth_header
746         end
747         assert_response :success
748       end
749     end
750
751     def test_upload_large_changeset
752       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(:id => 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(:id => 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(:id => 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     # when we make some simple changes we get the same changes back from the
1807     # diff download.
1808     def test_diff_download_simple
1809       node = create(:node)
1810
1811       ## First try with a non-public user, which should get a forbidden
1812       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
1813
1814       # create a temporary changeset
1815       xml = "<osm><changeset>" \
1816             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1817             "</changeset></osm>"
1818       put changeset_create_path, :params => xml, :headers => auth_header
1819       assert_response :forbidden
1820
1821       ## Now try with a normal user
1822       auth_header = basic_authorization_header create(:user).email, "test"
1823
1824       # create a temporary changeset
1825       xml = "<osm><changeset>" \
1826             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1827             "</changeset></osm>"
1828       put changeset_create_path, :params => xml, :headers => auth_header
1829       assert_response :success
1830       changeset_id = @response.body.to_i
1831
1832       # add a diff to it
1833       diff = <<~CHANGESET
1834         <osmChange>
1835          <modify>
1836           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset_id}' version='1'/>
1837           <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset_id}' version='2'/>
1838           <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset_id}' version='3'/>
1839           <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset_id}' version='4'/>
1840           <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset_id}' version='5'/>
1841           <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset_id}' version='6'/>
1842           <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset_id}' version='7'/>
1843           <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset_id}' version='8'/>
1844          </modify>
1845         </osmChange>
1846       CHANGESET
1847
1848       # upload it
1849       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1850       assert_response :success,
1851                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1852
1853       get changeset_download_path(:id => changeset_id)
1854       assert_response :success
1855
1856       assert_select "osmChange", 1
1857       assert_select "osmChange>modify", 8
1858       assert_select "osmChange>modify>node", 8
1859     end
1860
1861     ##
1862     # culled this from josm to ensure that nothing in the way that josm
1863     # is formatting the request is causing it to fail.
1864     #
1865     # NOTE: the error turned out to be something else completely!
1866     def test_josm_upload
1867       auth_header = basic_authorization_header create(:user).email, "test"
1868
1869       # create a temporary changeset
1870       xml = "<osm><changeset>" \
1871             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1872             "</changeset></osm>"
1873       put changeset_create_path, :params => xml, :headers => auth_header
1874       assert_response :success
1875       changeset_id = @response.body.to_i
1876
1877       diff = <<~OSMFILE
1878         <osmChange version="0.6" generator="JOSM">
1879         <create version="0.6" generator="JOSM">
1880           <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
1881           <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
1882           <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
1883           <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
1884           <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
1885           <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
1886           <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
1887           <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
1888           <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
1889           <way id='-10' action='modify' visible='true' changeset='#{changeset_id}'>
1890             <nd ref='-1' />
1891             <nd ref='-2' />
1892             <nd ref='-3' />
1893             <nd ref='-4' />
1894             <nd ref='-5' />
1895             <nd ref='-6' />
1896             <nd ref='-7' />
1897             <nd ref='-8' />
1898             <nd ref='-9' />
1899             <tag k='highway' v='residential' />
1900             <tag k='name' v='Foobar Street' />
1901           </way>
1902         </create>
1903         </osmChange>
1904       OSMFILE
1905
1906       # upload it
1907       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1908       assert_response :success,
1909                       "can't upload a diff from JOSM: #{@response.body}"
1910
1911       get changeset_download_path(:id => changeset_id)
1912       assert_response :success
1913
1914       assert_select "osmChange", 1
1915       assert_select "osmChange>create>node", 9
1916       assert_select "osmChange>create>way", 1
1917       assert_select "osmChange>create>way>nd", 9
1918       assert_select "osmChange>create>way>tag", 2
1919     end
1920
1921     ##
1922     # when we make some complex changes we get the same changes back from the
1923     # diff download.
1924     def test_diff_download_complex
1925       node = create(:node)
1926       node2 = create(:node)
1927       way = create(:way)
1928       auth_header = basic_authorization_header create(:user).email, "test"
1929
1930       # create a temporary changeset
1931       xml = "<osm><changeset>" \
1932             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1933             "</changeset></osm>"
1934       put changeset_create_path, :params => xml, :headers => auth_header
1935       assert_response :success
1936       changeset_id = @response.body.to_i
1937
1938       # add a diff to it
1939       diff = <<~CHANGESET
1940         <osmChange>
1941          <delete>
1942           <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset_id}' version='1'/>
1943          </delete>
1944          <create>
1945           <node id='-1' lon='0.9' lat='0.9' changeset='#{changeset_id}' version='0'/>
1946           <node id='-2' lon='0.8' lat='0.9' changeset='#{changeset_id}' version='0'/>
1947           <node id='-3' lon='0.7' lat='0.9' changeset='#{changeset_id}' version='0'/>
1948          </create>
1949          <modify>
1950           <node id='#{node2.id}' lon='2.0' lat='1.5' changeset='#{changeset_id}' version='1'/>
1951           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
1952            <nd ref='#{node2.id}'/>
1953            <nd ref='-1'/>
1954            <nd ref='-2'/>
1955            <nd ref='-3'/>
1956           </way>
1957          </modify>
1958         </osmChange>
1959       CHANGESET
1960
1961       # upload it
1962       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1963       assert_response :success,
1964                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1965
1966       get changeset_download_path(:id => changeset_id)
1967       assert_response :success
1968
1969       assert_select "osmChange", 1
1970       assert_select "osmChange>create", 3
1971       assert_select "osmChange>delete", 1
1972       assert_select "osmChange>modify", 2
1973       assert_select "osmChange>create>node", 3
1974       assert_select "osmChange>delete>node", 1
1975       assert_select "osmChange>modify>node", 1
1976       assert_select "osmChange>modify>way", 1
1977     end
1978
1979     def test_changeset_download
1980       changeset = create(:changeset)
1981       node = create(:node, :with_history, :version => 1, :changeset => changeset)
1982       tag = create(:old_node_tag, :old_node => node.old_nodes.find_by(:version => 1))
1983       node2 = create(:node, :with_history, :version => 1, :changeset => changeset)
1984       _node3 = create(:node, :with_history, :deleted, :version => 1, :changeset => changeset)
1985       _relation = create(:relation, :with_history, :version => 1, :changeset => changeset)
1986       _relation2 = create(:relation, :with_history, :deleted, :version => 1, :changeset => changeset)
1987
1988       get changeset_download_path(changeset)
1989
1990       assert_response :success
1991
1992       # FIXME: needs more assert_select tests
1993       assert_select "osmChange[version='#{Settings.api_version}'][generator='#{Settings.generator}']" do
1994         assert_select "create", :count => 5
1995         assert_select "create>node[id='#{node.id}'][visible='#{node.visible?}'][version='#{node.version}']" do
1996           assert_select "tag[k='#{tag.k}'][v='#{tag.v}']"
1997         end
1998         assert_select "create>node[id='#{node2.id}']"
1999       end
2000     end
2001
2002     test "sorts downloaded elements by timestamp" do
2003       changeset = create(:changeset)
2004       node1 = create(:old_node, :version => 2, :timestamp => "2020-02-01", :changeset => changeset)
2005       node0 = create(:old_node, :version => 2, :timestamp => "2020-01-01", :changeset => changeset)
2006
2007       get changeset_download_path(changeset)
2008       assert_response :success
2009       assert_dom "modify", :count => 2 do |modify|
2010         assert_dom modify[0], ">node", :count => 1 do |node|
2011           assert_dom node, ">@id", node0.node_id.to_s
2012         end
2013         assert_dom modify[1], ">node", :count => 1 do |node|
2014           assert_dom node, ">@id", node1.node_id.to_s
2015         end
2016       end
2017     end
2018
2019     test "sorts downloaded elements by version" do
2020       changeset = create(:changeset)
2021       node1 = create(:old_node, :version => 3, :timestamp => "2020-01-01", :changeset => changeset)
2022       node0 = create(:old_node, :version => 2, :timestamp => "2020-01-01", :changeset => changeset)
2023
2024       get changeset_download_path(changeset)
2025       assert_response :success
2026       assert_dom "modify", :count => 2 do |modify|
2027         assert_dom modify[0], ">node", :count => 1 do |node|
2028           assert_dom node, ">@id", node0.node_id.to_s
2029         end
2030         assert_dom modify[1], ">node", :count => 1 do |node|
2031           assert_dom node, ">@id", node1.node_id.to_s
2032         end
2033       end
2034     end
2035
2036     ##
2037     # check that the bounding box of a changeset gets updated correctly
2038     # FIXME: This should really be moded to a integration test due to the with_controller
2039     def test_changeset_bbox
2040       way = create(:way)
2041       create(:way_node, :way => way, :node => create(:node, :lat => 0.3, :lon => 0.3))
2042
2043       auth_header = basic_authorization_header create(:user).email, "test"
2044
2045       # create a new changeset
2046       xml = "<osm><changeset/></osm>"
2047       put changeset_create_path, :params => xml, :headers => auth_header
2048       assert_response :success, "Creating of changeset failed."
2049       changeset_id = @response.body.to_i
2050
2051       # add a single node to it
2052       with_controller(NodesController.new) do
2053         xml = "<osm><node lon='0.1' lat='0.2' changeset='#{changeset_id}'/></osm>"
2054         put node_create_path, :params => xml, :headers => auth_header
2055         assert_response :success, "Couldn't create node."
2056       end
2057
2058       # get the bounding box back from the changeset
2059       get changeset_show_path(:id => changeset_id)
2060       assert_response :success, "Couldn't read back changeset."
2061       assert_select "osm>changeset[min_lon='0.1000000']", 1
2062       assert_select "osm>changeset[max_lon='0.1000000']", 1
2063       assert_select "osm>changeset[min_lat='0.2000000']", 1
2064       assert_select "osm>changeset[max_lat='0.2000000']", 1
2065
2066       # add another node to it
2067       with_controller(NodesController.new) do
2068         xml = "<osm><node lon='0.2' lat='0.1' changeset='#{changeset_id}'/></osm>"
2069         put node_create_path, :params => xml, :headers => auth_header
2070         assert_response :success, "Couldn't create second node."
2071       end
2072
2073       # get the bounding box back from the changeset
2074       get changeset_show_path(:id => changeset_id)
2075       assert_response :success, "Couldn't read back changeset for the second time."
2076       assert_select "osm>changeset[min_lon='0.1000000']", 1
2077       assert_select "osm>changeset[max_lon='0.2000000']", 1
2078       assert_select "osm>changeset[min_lat='0.1000000']", 1
2079       assert_select "osm>changeset[max_lat='0.2000000']", 1
2080
2081       # add (delete) a way to it, which contains a point at (3,3)
2082       with_controller(WaysController.new) do
2083         xml = update_changeset(xml_for_way(way), changeset_id)
2084         delete api_way_path(way), :params => xml.to_s, :headers => auth_header
2085         assert_response :success, "Couldn't delete a way."
2086       end
2087
2088       # get the bounding box back from the changeset
2089       get changeset_show_path(:id => changeset_id)
2090       assert_response :success, "Couldn't read back changeset for the third time."
2091       assert_select "osm>changeset[min_lon='0.1000000']", 1
2092       assert_select "osm>changeset[max_lon='0.3000000']", 1
2093       assert_select "osm>changeset[min_lat='0.1000000']", 1
2094       assert_select "osm>changeset[max_lat='0.3000000']", 1
2095     end
2096
2097     ##
2098     # test the query functionality of changesets
2099     def test_query
2100       private_user = create(:user, :data_public => false)
2101       private_user_changeset = create(:changeset, :user => private_user)
2102       private_user_closed_changeset = create(:changeset, :closed, :user => private_user)
2103       user = create(:user)
2104       changeset = create(:changeset, :user => user)
2105       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))
2106       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)
2107       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)
2108
2109       get changesets_path(:bbox => "-10,-10, 10, 10")
2110       assert_response :success, "can't get changesets in bbox"
2111       assert_changesets [changeset2, changeset3]
2112
2113       get changesets_path(:bbox => "4.5,4.5,4.6,4.6")
2114       assert_response :success, "can't get changesets in bbox"
2115       assert_changesets [changeset3]
2116
2117       # not found when looking for changesets of non-existing users
2118       get changesets_path(:user => User.maximum(:id) + 1)
2119       assert_response :not_found
2120       assert_equal "text/plain", @response.media_type
2121       get changesets_path(:display_name => " ")
2122       assert_response :not_found
2123       assert_equal "text/plain", @response.media_type
2124
2125       # can't get changesets of user 1 without authenticating
2126       get changesets_path(:user => private_user.id)
2127       assert_response :not_found, "shouldn't be able to get changesets by non-public user (ID)"
2128       get changesets_path(:display_name => private_user.display_name)
2129       assert_response :not_found, "shouldn't be able to get changesets by non-public user (name)"
2130
2131       # but this should work
2132       auth_header = basic_authorization_header private_user.email, "test"
2133       get changesets_path(:user => private_user.id), :headers => auth_header
2134       assert_response :success, "can't get changesets by user ID"
2135       assert_changesets [private_user_changeset, private_user_closed_changeset]
2136
2137       get changesets_path(:display_name => private_user.display_name), :headers => auth_header
2138       assert_response :success, "can't get changesets by user name"
2139       assert_changesets [private_user_changeset, private_user_closed_changeset]
2140
2141       # test json endpoint
2142       get changesets_path(:display_name => private_user.display_name), :headers => auth_header, :params => { :format => "json" }
2143       assert_response :success, "can't get changesets by user name"
2144
2145       js = ActiveSupport::JSON.decode(@response.body)
2146       assert_not_nil js
2147
2148       assert_equal Settings.api_version, js["version"]
2149       assert_equal Settings.generator, js["generator"]
2150       assert_equal 2, js["changesets"].count
2151
2152       # check that the correct error is given when we provide both UID and name
2153       get changesets_path(:user => private_user.id,
2154                           :display_name => private_user.display_name), :headers => auth_header
2155       assert_response :bad_request, "should be a bad request to have both ID and name specified"
2156
2157       get changesets_path(:user => private_user.id, :open => true), :headers => auth_header
2158       assert_response :success, "can't get changesets by user and open"
2159       assert_changesets [private_user_changeset]
2160
2161       get changesets_path(:time => "2007-12-31"), :headers => auth_header
2162       assert_response :success, "can't get changesets by time-since"
2163       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
2164
2165       get changesets_path(:time => "2008-01-01T12:34Z"), :headers => auth_header
2166       assert_response :success, "can't get changesets by time-since with hour"
2167       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
2168
2169       get changesets_path(:time => "2007-12-31T23:59Z,2008-01-02T00:01Z"), :headers => auth_header
2170       assert_response :success, "can't get changesets by time-range"
2171       assert_changesets [closed_changeset]
2172
2173       get changesets_path(:open => "true"), :headers => auth_header
2174       assert_response :success, "can't get changesets by open-ness"
2175       assert_changesets [private_user_changeset, changeset, changeset2, changeset3]
2176
2177       get changesets_path(:closed => "true"), :headers => auth_header
2178       assert_response :success, "can't get changesets by closed-ness"
2179       assert_changesets [private_user_closed_changeset, closed_changeset]
2180
2181       get changesets_path(:closed => "true", :user => private_user.id), :headers => auth_header
2182       assert_response :success, "can't get changesets by closed-ness and user"
2183       assert_changesets [private_user_closed_changeset]
2184
2185       get changesets_path(:closed => "true", :user => user.id), :headers => auth_header
2186       assert_response :success, "can't get changesets by closed-ness and user"
2187       assert_changesets [closed_changeset]
2188
2189       get changesets_path(:changesets => "#{private_user_changeset.id},#{changeset.id},#{closed_changeset.id}"), :headers => auth_header
2190       assert_response :success, "can't get changesets by id (as comma-separated string)"
2191       assert_changesets [private_user_changeset, changeset, closed_changeset]
2192
2193       get changesets_path(:changesets => ""), :headers => auth_header
2194       assert_response :bad_request, "should be a bad request since changesets is empty"
2195     end
2196
2197     ##
2198     # test the query functionality of changesets with the limit parameter
2199     def test_query_limit
2200       user = create(:user)
2201       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))
2202       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))
2203       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))
2204       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))
2205       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))
2206
2207       get changesets_path
2208       assert_response :success
2209       assert_changesets_in_order [changeset5, changeset4, changeset3, changeset2, changeset1]
2210
2211       get changesets_path(:limit => "3")
2212       assert_response :success
2213       assert_changesets_in_order [changeset5, changeset4, changeset3]
2214
2215       get changesets_path(:limit => "0")
2216       assert_response :bad_request
2217
2218       get changesets_path(:limit => Settings.max_changeset_query_limit)
2219       assert_response :success
2220       assert_changesets_in_order [changeset5, changeset4, changeset3, changeset2, changeset1]
2221
2222       get changesets_path(:limit => Settings.max_changeset_query_limit + 1)
2223       assert_response :bad_request
2224     end
2225
2226     ##
2227     # test the query functionality of sequential changesets with order and time parameters
2228     def test_query_order
2229       user = create(:user)
2230       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))
2231       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))
2232       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))
2233       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))
2234       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))
2235       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))
2236
2237       get changesets_path
2238       assert_response :success
2239       assert_changesets_in_order [changeset6, changeset5, changeset4, changeset3, changeset2, changeset1]
2240
2241       get changesets_path(:order => "oldest")
2242       assert_response :success
2243       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4, changeset5, changeset6]
2244
2245       [
2246         # lower time bound at the opening time of a changeset
2247         ["2008-02-01T00:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3, changeset2]],
2248         # lower time bound in the middle of a changeset
2249         ["2008-02-01T12:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3]],
2250         # lower time bound at the closing time of a changeset
2251         ["2008-02-02T00:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3]],
2252         # lower time bound after the closing time of a changeset
2253         ["2008-02-02T00:00:01Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3], [changeset5, changeset4, changeset3]],
2254         # upper time bound in the middle of a changeset
2255         ["2007-09-09T12:00:00Z", "2008-04-01T12:00:00Z", [changeset4, changeset3, changeset2, changeset1], [changeset4, changeset3, changeset2, changeset1]],
2256         # empty range
2257         ["2009-02-02T00:00:01Z", "2018-05-15T00:00:00Z", [], []]
2258       ].each do |from, to, interval_changesets, point_changesets|
2259         get changesets_path(:time => "#{from},#{to}")
2260         assert_response :success
2261         assert_changesets_in_order interval_changesets
2262
2263         get changesets_path(:from => from, :to => to)
2264         assert_response :success
2265         assert_changesets_in_order point_changesets
2266
2267         get changesets_path(:from => from, :to => to, :order => "oldest")
2268         assert_response :success
2269         assert_changesets_in_order point_changesets.reverse
2270       end
2271     end
2272
2273     ##
2274     # test the query functionality of overlapping changesets with order and time parameters
2275     def test_query_order_overlapping
2276       user = create(:user)
2277       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))
2278       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))
2279       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))
2280       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))
2281       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))
2282
2283       get changesets_path(:time => "2015-06-04T00:00:00Z")
2284       assert_response :success
2285       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4]
2286
2287       get changesets_path(:from => "2015-06-04T00:00:00Z")
2288       assert_response :success
2289       assert_changesets_in_order [changeset1, changeset2, changeset3]
2290
2291       get changesets_path(:from => "2015-06-04T00:00:00Z", :order => "oldest")
2292       assert_response :success
2293       assert_changesets_in_order [changeset3, changeset2, changeset1]
2294
2295       get changesets_path(:time => "2015-06-04T16:00:00Z,2015-06-04T17:30:00Z")
2296       assert_response :success
2297       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4]
2298
2299       get changesets_path(:from => "2015-06-04T16:00:00Z", :to => "2015-06-04T17:30:00Z")
2300       assert_response :success
2301       assert_changesets_in_order [changeset1, changeset2]
2302
2303       get changesets_path(:from => "2015-06-04T16:00:00Z", :to => "2015-06-04T17:30:00Z", :order => "oldest")
2304       assert_response :success
2305       assert_changesets_in_order [changeset2, changeset1]
2306     end
2307
2308     ##
2309     # check that errors are returned if garbage is inserted
2310     # into query strings
2311     def test_query_invalid
2312       ["abracadabra!",
2313        "1,2,3,F",
2314        ";drop table users;"].each do |bbox|
2315         get changesets_path(:bbox => bbox)
2316         assert_response :bad_request, "'#{bbox}' isn't a bbox"
2317       end
2318
2319       ["now()",
2320        "00-00-00",
2321        ";drop table users;",
2322        ",",
2323        "-,-"].each do |time|
2324         get changesets_path(:time => time)
2325         assert_response :bad_request, "'#{time}' isn't a valid time range"
2326       end
2327
2328       ["me",
2329        "foobar",
2330        "-1",
2331        "0"].each do |uid|
2332         get changesets_path(:user => uid)
2333         assert_response :bad_request, "'#{uid}' isn't a valid user ID"
2334       end
2335
2336       get changesets_path(:order => "oldest", :time => "2008-01-01T00:00Z,2018-01-01T00:00Z")
2337       assert_response :bad_request, "cannot use order=oldest with time"
2338     end
2339
2340     ##
2341     # check updating tags on a changeset
2342     def test_changeset_update
2343       private_user = create(:user, :data_public => false)
2344       private_changeset = create(:changeset, :user => private_user)
2345       user = create(:user)
2346       changeset = create(:changeset, :user => user)
2347
2348       ## First try with a non-public user
2349       new_changeset = create_changeset_xml(:user => private_user)
2350       new_tag = XML::Node.new "tag"
2351       new_tag["k"] = "tagtesting"
2352       new_tag["v"] = "valuetesting"
2353       new_changeset.find("//osm/changeset").first << new_tag
2354
2355       # try without any authorization
2356       put changeset_show_path(private_changeset), :params => new_changeset.to_s
2357       assert_response :unauthorized
2358
2359       # try with the wrong authorization
2360       auth_header = basic_authorization_header create(:user).email, "test"
2361       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2362       assert_response :conflict
2363
2364       # now this should get an unauthorized
2365       auth_header = basic_authorization_header private_user.email, "test"
2366       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2367       assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
2368
2369       ## Now try with the public user
2370       new_changeset = create_changeset_xml(:id => 1)
2371       new_tag = XML::Node.new "tag"
2372       new_tag["k"] = "tagtesting"
2373       new_tag["v"] = "valuetesting"
2374       new_changeset.find("//osm/changeset").first << new_tag
2375
2376       # try without any authorization
2377       put changeset_show_path(changeset), :params => new_changeset.to_s
2378       assert_response :unauthorized
2379
2380       # try with the wrong authorization
2381       auth_header = basic_authorization_header create(:user).email, "test"
2382       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2383       assert_response :conflict
2384
2385       # now this should work...
2386       auth_header = basic_authorization_header user.email, "test"
2387       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2388       assert_response :success
2389
2390       assert_select "osm>changeset[id='#{changeset.id}']", 1
2391       assert_select "osm>changeset>tag", 1
2392       assert_select "osm>changeset>tag[k='tagtesting'][v='valuetesting']", 1
2393     end
2394
2395     ##
2396     # check that a user different from the one who opened the changeset
2397     # can't modify it.
2398     def test_changeset_update_invalid
2399       auth_header = basic_authorization_header create(:user).email, "test"
2400
2401       changeset = create(:changeset)
2402       new_changeset = create_changeset_xml(:user => changeset.user, :id => changeset.id)
2403       new_tag = XML::Node.new "tag"
2404       new_tag["k"] = "testing"
2405       new_tag["v"] = "testing"
2406       new_changeset.find("//osm/changeset").first << new_tag
2407
2408       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2409       assert_response :conflict
2410     end
2411
2412     ##
2413     # check that a changeset can contain a certain max number of changes.
2414     ## FIXME should be changed to an integration test due to the with_controller
2415     def test_changeset_limits
2416       user = create(:user)
2417       auth_header = basic_authorization_header user.email, "test"
2418
2419       # create an old changeset to ensure we have the maximum rate limit
2420       create(:changeset, :user => user, :created_at => Time.now.utc - 28.days)
2421
2422       # open a new changeset
2423       xml = "<osm><changeset/></osm>"
2424       put changeset_create_path, :params => xml, :headers => auth_header
2425       assert_response :success, "can't create a new changeset"
2426       cs_id = @response.body.to_i
2427
2428       # start the counter just short of where the changeset should finish.
2429       offset = 10
2430       # alter the database to set the counter on the changeset directly,
2431       # otherwise it takes about 6 minutes to fill all of them.
2432       changeset = Changeset.find(cs_id)
2433       changeset.num_changes = Changeset::MAX_ELEMENTS - offset
2434       changeset.save!
2435
2436       with_controller(NodesController.new) do
2437         # create a new node
2438         xml = "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
2439         put node_create_path, :params => xml, :headers => auth_header
2440         assert_response :success, "can't create a new node"
2441         node_id = @response.body.to_i
2442
2443         get api_node_path(node_id)
2444         assert_response :success, "can't read back new node"
2445         node_doc = XML::Parser.string(@response.body).parse
2446         node_xml = node_doc.find("//osm/node").first
2447
2448         # loop until we fill the changeset with nodes
2449         offset.times do |i|
2450           node_xml["lat"] = rand.to_s
2451           node_xml["lon"] = rand.to_s
2452           node_xml["version"] = (i + 1).to_s
2453
2454           put api_node_path(node_id), :params => node_doc.to_s, :headers => auth_header
2455           assert_response :success, "attempt #{i} should have succeeded"
2456         end
2457
2458         # trying again should fail
2459         node_xml["lat"] = rand.to_s
2460         node_xml["lon"] = rand.to_s
2461         node_xml["version"] = offset.to_s
2462
2463         put api_node_path(node_id), :params => node_doc.to_s, :headers => auth_header
2464         assert_response :conflict, "final attempt should have failed"
2465       end
2466
2467       changeset = Changeset.find(cs_id)
2468       assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
2469
2470       # check that the changeset is now closed as well
2471       assert_not(changeset.open?,
2472                  "changeset should have been auto-closed by exceeding " \
2473                  "element limit.")
2474     end
2475
2476     ##
2477     # check that the changeset download for a changeset with a redacted
2478     # element in it doesn't contain that element.
2479     def test_diff_download_redacted
2480       changeset = create(:changeset)
2481       node = create(:node, :with_history, :version => 2, :changeset => changeset)
2482       node_v1 = node.old_nodes.find_by(:version => 1)
2483       node_v1.redact!(create(:redaction))
2484
2485       get changeset_download_path(changeset)
2486       assert_response :success
2487
2488       assert_select "osmChange", 1
2489       # this changeset contains the node in versions 1 & 2, but 1 should
2490       # be hidden.
2491       assert_select "osmChange node[id='#{node.id}']", 1
2492       assert_select "osmChange node[id='#{node.id}'][version='1']", 0
2493     end
2494
2495     ##
2496     # test subscribe success
2497     def test_subscribe_success
2498       auth_header = basic_authorization_header create(:user).email, "test"
2499       changeset = create(:changeset, :closed)
2500
2501       assert_difference "changeset.subscribers.count", 1 do
2502         post api_changeset_subscribe_path(changeset), :headers => auth_header
2503       end
2504       assert_response :success
2505
2506       # not closed changeset
2507       changeset = create(:changeset)
2508       assert_difference "changeset.subscribers.count", 1 do
2509         post api_changeset_subscribe_path(changeset), :headers => auth_header
2510       end
2511       assert_response :success
2512     end
2513
2514     ##
2515     # test subscribe fail
2516     def test_subscribe_fail
2517       user = create(:user)
2518
2519       # unauthorized
2520       changeset = create(:changeset, :closed)
2521       assert_no_difference "changeset.subscribers.count" do
2522         post api_changeset_subscribe_path(changeset)
2523       end
2524       assert_response :unauthorized
2525
2526       auth_header = basic_authorization_header user.email, "test"
2527
2528       # bad changeset id
2529       assert_no_difference "changeset.subscribers.count" do
2530         post api_changeset_subscribe_path(:id => 999111), :headers => auth_header
2531       end
2532       assert_response :not_found
2533
2534       # trying to subscribe when already subscribed
2535       changeset = create(:changeset, :closed)
2536       changeset.subscribers.push(user)
2537       assert_no_difference "changeset.subscribers.count" do
2538         post api_changeset_subscribe_path(changeset), :headers => auth_header
2539       end
2540       assert_response :conflict
2541     end
2542
2543     ##
2544     # test unsubscribe success
2545     def test_unsubscribe_success
2546       user = create(:user)
2547       auth_header = basic_authorization_header user.email, "test"
2548       changeset = create(:changeset, :closed)
2549       changeset.subscribers.push(user)
2550
2551       assert_difference "changeset.subscribers.count", -1 do
2552         post api_changeset_unsubscribe_path(changeset), :headers => auth_header
2553       end
2554       assert_response :success
2555
2556       # not closed changeset
2557       changeset = create(:changeset)
2558       changeset.subscribers.push(user)
2559
2560       assert_difference "changeset.subscribers.count", -1 do
2561         post api_changeset_unsubscribe_path(changeset), :headers => auth_header
2562       end
2563       assert_response :success
2564     end
2565
2566     ##
2567     # test unsubscribe fail
2568     def test_unsubscribe_fail
2569       # unauthorized
2570       changeset = create(:changeset, :closed)
2571       assert_no_difference "changeset.subscribers.count" do
2572         post api_changeset_unsubscribe_path(changeset)
2573       end
2574       assert_response :unauthorized
2575
2576       auth_header = basic_authorization_header create(:user).email, "test"
2577
2578       # bad changeset id
2579       assert_no_difference "changeset.subscribers.count" do
2580         post api_changeset_unsubscribe_path(:id => 999111), :headers => auth_header
2581       end
2582       assert_response :not_found
2583
2584       # trying to unsubscribe when not subscribed
2585       changeset = create(:changeset, :closed)
2586       assert_no_difference "changeset.subscribers.count" do
2587         post api_changeset_unsubscribe_path(changeset), :headers => auth_header
2588       end
2589       assert_response :not_found
2590     end
2591
2592     private
2593
2594     ##
2595     # check that the output consists of one specific changeset
2596     def assert_single_changeset(changeset)
2597       assert_select "osm>changeset", 1
2598       assert_select "osm>changeset>@id", changeset.id.to_s
2599       assert_select "osm>changeset>@created_at", changeset.created_at.xmlschema
2600       if changeset.open?
2601         assert_select "osm>changeset>@open", "true"
2602         assert_select "osm>changeset>@closed_at", 0
2603       else
2604         assert_select "osm>changeset>@open", "false"
2605         assert_select "osm>changeset>@closed_at", changeset.closed_at.xmlschema
2606       end
2607     end
2608
2609     def assert_single_changeset_json(changeset, js)
2610       assert_equal changeset.id, js["changeset"]["id"]
2611       assert_equal changeset.created_at.xmlschema, js["changeset"]["created_at"]
2612       if changeset.open?
2613         assert js["changeset"]["open"]
2614         assert_nil js["changeset"]["closed_at"]
2615       else
2616         assert_not js["changeset"]["open"]
2617         assert_equal changeset.closed_at.xmlschema, js["changeset"]["closed_at"]
2618       end
2619     end
2620
2621     ##
2622     # check that certain changesets exist in the output
2623     def assert_changesets(changesets)
2624       assert_select "osm>changeset", changesets.size
2625       changesets.each do |changeset|
2626         assert_select "osm>changeset[id='#{changeset.id}']", 1
2627       end
2628     end
2629
2630     ##
2631     # check that certain changesets exist in the output in the specified order
2632     def assert_changesets_in_order(changesets)
2633       assert_select "osm>changeset", changesets.size
2634       changesets.each_with_index do |changeset, index|
2635         assert_select "osm>changeset:nth-child(#{index + 1})[id='#{changeset.id}']", 1
2636       end
2637     end
2638
2639     ##
2640     # update the changeset_id of a way element
2641     def update_changeset(xml, changeset_id)
2642       xml_attr_rewrite(xml, "changeset", changeset_id)
2643     end
2644
2645     ##
2646     # update an attribute in a way element
2647     def xml_attr_rewrite(xml, name, value)
2648       xml.find("//osm/way").first[name] = value.to_s
2649       xml
2650     end
2651
2652     ##
2653     # build XML for changesets
2654     def create_changeset_xml(user: nil, id: nil)
2655       root = XML::Document.new
2656       root.root = XML::Node.new "osm"
2657       cs = XML::Node.new "changeset"
2658       if user
2659         cs["user"] = user.display_name
2660         cs["uid"] = user.id.to_s
2661       end
2662       cs["id"] = id.to_s if id
2663       root.root << cs
2664       root
2665     end
2666   end
2667 end