]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets_controller_test.rb
Merge branch 'master' of github.com:openstreetmap/openstreetmap-website into mobile...
[rails.git] / test / controllers / api / changesets_controller_test.rb
1 require "test_helper"
2
3 module Api
4   class ChangesetsControllerTest < ActionController::TestCase
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/subscribe", :method => :post },
26         { :controller => "api/changesets", :action => "subscribe", :id => "1" }
27       )
28       assert_routing(
29         { :path => "/api/0.6/changeset/1/unsubscribe", :method => :post },
30         { :controller => "api/changesets", :action => "unsubscribe", :id => "1" }
31       )
32       assert_routing(
33         { :path => "/api/0.6/changeset/1", :method => :put },
34         { :controller => "api/changesets", :action => "update", :id => "1" }
35       )
36       assert_routing(
37         { :path => "/api/0.6/changeset/1/close", :method => :put },
38         { :controller => "api/changesets", :action => "close", :id => "1" }
39       )
40       assert_routing(
41         { :path => "/api/0.6/changesets", :method => :get },
42         { :controller => "api/changesets", :action => "query" }
43       )
44     end
45
46     # -----------------------
47     # Test simple changeset creation
48     # -----------------------
49
50     def test_create
51       basic_authorization create(:user, :data_public => false).email, "test"
52       # Create the first user's changeset
53       xml = "<osm><changeset>" \
54             "<tag k='created_by' v='osm test suite checking changesets'/>" \
55             "</changeset></osm>"
56       put :create, :body => xml
57       assert_require_public_data
58
59       basic_authorization create(:user).email, "test"
60       # Create the first user's changeset
61       xml = "<osm><changeset>" \
62             "<tag k='created_by' v='osm test suite checking changesets'/>" \
63             "</changeset></osm>"
64       put :create, :body => xml
65
66       assert_response :success, "Creation of changeset did not return sucess status"
67       newid = @response.body.to_i
68
69       # check end time, should be an hour ahead of creation time
70       cs = Changeset.find(newid)
71       duration = cs.closed_at - cs.created_at
72       # the difference can either be a rational, or a floating point number
73       # of seconds, depending on the code path taken :-(
74       if duration.class == Rational
75         assert_equal Rational(1, 24), duration, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
76       else
77         # must be number of seconds...
78         assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
79       end
80
81       # checks if uploader was subscribed
82       assert_equal 1, cs.subscribers.length
83     end
84
85     def test_create_invalid
86       basic_authorization create(:user, :data_public => false).email, "test"
87       xml = "<osm><changeset></osm>"
88       put :create, :body => xml
89       assert_require_public_data
90
91       ## Try the public user
92       basic_authorization create(:user).email, "test"
93       xml = "<osm><changeset></osm>"
94       put :create, :body => xml
95       assert_response :bad_request, "creating a invalid changeset should fail"
96     end
97
98     def test_create_invalid_no_content
99       ## First check with no auth
100       put :create
101       assert_response :unauthorized, "shouldn't be able to create a changeset with no auth"
102
103       ## Now try to with a non-public user
104       basic_authorization create(:user, :data_public => false).email, "test"
105       put :create
106       assert_require_public_data
107
108       ## Try an inactive user
109       basic_authorization create(:user, :pending).email, "test"
110       put :create
111       assert_inactive_user
112
113       ## Now try to use a normal user
114       basic_authorization create(:user).email, "test"
115       put :create
116       assert_response :bad_request, "creating a changeset with no content should fail"
117     end
118
119     def test_create_wrong_method
120       basic_authorization create(:user).email, "test"
121       get :create
122       assert_response :method_not_allowed
123       post :create
124       assert_response :method_not_allowed
125     end
126
127     ##
128     # check that the changeset can be shown and returns the correct
129     # document structure.
130     def test_show
131       changeset = create(:changeset)
132
133       get :show, :params => { :id => changeset.id }
134       assert_response :success, "cannot get first changeset"
135
136       assert_select "osm[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
137       assert_select "osm>changeset[id='#{changeset.id}']", 1
138       assert_select "osm>changeset>@open", "true"
139       assert_select "osm>changeset>@created_at", changeset.created_at.xmlschema
140       assert_select "osm>changeset>@closed_at", 0
141       assert_select "osm>changeset>discussion", 0
142
143       get :show, :params => { :id => changeset.id, :include_discussion => true }
144       assert_response :success, "cannot get first changeset with comments"
145
146       assert_select "osm[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
147       assert_select "osm>changeset[id='#{changeset.id}']", 1
148       assert_select "osm>changeset>@open", "true"
149       assert_select "osm>changeset>@created_at", changeset.created_at.xmlschema
150       assert_select "osm>changeset>@closed_at", 0
151       assert_select "osm>changeset>discussion", 1
152       assert_select "osm>changeset>discussion>comment", 0
153
154       changeset = create(:changeset, :closed)
155       create_list(:changeset_comment, 3, :changeset_id => changeset.id)
156
157       get :show, :params => { :id => changeset.id, :include_discussion => true }
158       assert_response :success, "cannot get closed changeset with comments"
159
160       assert_select "osm[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
161       assert_select "osm>changeset[id='#{changeset.id}']", 1
162       assert_select "osm>changeset>@open", "false"
163       assert_select "osm>changeset>@created_at", changeset.created_at.xmlschema
164       assert_select "osm>changeset>@closed_at", changeset.closed_at.xmlschema
165       assert_select "osm>changeset>discussion", 1
166       assert_select "osm>changeset>discussion>comment", 3
167     end
168
169     ##
170     # check that a changeset that doesn't exist returns an appropriate message
171     def test_show_not_found
172       [0, -32, 233455644, "afg", "213"].each do |id|
173         get :show, :params => { :id => id }
174         assert_response :not_found, "should get a not found"
175       rescue ActionController::UrlGenerationError => e
176         assert_match(/No route matches/, e.to_s)
177       end
178     end
179
180     ##
181     # test that the user who opened a change can close it
182     def test_close
183       private_user = create(:user, :data_public => false)
184       private_changeset = create(:changeset, :user => private_user)
185       user = create(:user)
186       changeset = create(:changeset, :user => user)
187
188       ## Try without authentication
189       put :close, :params => { :id => changeset.id }
190       assert_response :unauthorized
191
192       ## Try using the non-public user
193       basic_authorization private_user.email, "test"
194       put :close, :params => { :id => private_changeset.id }
195       assert_require_public_data
196
197       ## The try with the public user
198       basic_authorization user.email, "test"
199
200       cs_id = changeset.id
201       put :close, :params => { :id => cs_id }
202       assert_response :success
203
204       # test that it really is closed now
205       cs = Changeset.find(cs_id)
206       assert_not(cs.is_open?,
207                  "changeset should be closed now (#{cs.closed_at} > #{Time.now.getutc}.")
208     end
209
210     ##
211     # test that a different user can't close another user's changeset
212     def test_close_invalid
213       user = create(:user)
214       changeset = create(:changeset)
215
216       basic_authorization user.email, "test"
217
218       put :close, :params => { :id => changeset.id }
219       assert_response :conflict
220       assert_equal "The user doesn't own that changeset", @response.body
221     end
222
223     ##
224     # test that you can't close using another method
225     def test_close_method_invalid
226       user = create(:user)
227       changeset = create(:changeset, :user => user)
228
229       basic_authorization user.email, "test"
230
231       get :close, :params => { :id => changeset.id }
232       assert_response :method_not_allowed
233
234       post :close, :params => { :id => changeset.id }
235       assert_response :method_not_allowed
236     end
237
238     ##
239     # check that you can't close a changeset that isn't found
240     def test_close_not_found
241       cs_ids = [0, -132, "123"]
242
243       # First try to do it with no auth
244       cs_ids.each do |id|
245         put :close, :params => { :id => id }
246         assert_response :unauthorized, "Shouldn't be able close the non-existant changeset #{id}, when not authorized"
247       rescue ActionController::UrlGenerationError => e
248         assert_match(/No route matches/, e.to_s)
249       end
250
251       # Now try with auth
252       basic_authorization create(:user).email, "test"
253       cs_ids.each do |id|
254         put :close, :params => { :id => id }
255         assert_response :not_found, "The changeset #{id} doesn't exist, so can't be closed"
256       rescue ActionController::UrlGenerationError => e
257         assert_match(/No route matches/, e.to_s)
258       end
259     end
260
261     ##
262     # upload something simple, but valid and check that it can
263     # be read back ok
264     # Also try without auth and another user.
265     def test_upload_simple_valid
266       private_user = create(:user, :data_public => false)
267       private_changeset = create(:changeset, :user => private_user)
268       user = create(:user)
269       changeset = create(:changeset, :user => user)
270
271       node = create(:node)
272       way = create(:way)
273       relation = create(:relation)
274       other_relation = create(:relation)
275       # create some tags, since we test that they are removed later
276       create(:node_tag, :node => node)
277       create(:way_tag, :way => way)
278       create(:relation_tag, :relation => relation)
279
280       ## Try with no auth
281       changeset_id = changeset.id
282
283       # simple diff to change a node, way and relation by removing
284       # their tags
285       diff = <<~CHANGESET
286         <osmChange>
287          <modify>
288           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
289           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
290            <nd ref='#{node.id}'/>
291           </way>
292          </modify>
293          <modify>
294           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
295            <member type='way' role='some' ref='#{way.id}'/>
296            <member type='node' role='some' ref='#{node.id}'/>
297            <member type='relation' role='some' ref='#{other_relation.id}'/>
298           </relation>
299          </modify>
300         </osmChange>
301       CHANGESET
302
303       # upload it
304       post :upload, :params => { :id => changeset_id }, :body => diff
305       assert_response :unauthorized,
306                       "shouldn't be able to upload a simple valid diff to changeset: #{@response.body}"
307
308       ## Now try with a private user
309       basic_authorization private_user.email, "test"
310       changeset_id = private_changeset.id
311
312       # simple diff to change a node, way and relation by removing
313       # their tags
314       diff = <<~CHANGESET
315         <osmChange>
316          <modify>
317           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
318           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
319            <nd ref='#{node.id}'/>
320           </way>
321          </modify>
322          <modify>
323           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
324            <member type='way' role='some' ref='#{way.id}'/>
325            <member type='node' role='some' ref='#{node.id}'/>
326            <member type='relation' role='some' ref='#{other_relation.id}'/>
327           </relation>
328          </modify>
329         </osmChange>
330       CHANGESET
331
332       # upload it
333       post :upload, :params => { :id => changeset_id }, :body => diff
334       assert_response :forbidden,
335                       "can't upload a simple valid diff to changeset: #{@response.body}"
336
337       ## Now try with the public user
338       basic_authorization user.email, "test"
339       changeset_id = changeset.id
340
341       # simple diff to change a node, way and relation by removing
342       # their tags
343       diff = <<~CHANGESET
344         <osmChange>
345          <modify>
346           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
347           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
348            <nd ref='#{node.id}'/>
349           </way>
350          </modify>
351          <modify>
352           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
353            <member type='way' role='some' ref='#{way.id}'/>
354            <member type='node' role='some' ref='#{node.id}'/>
355            <member type='relation' role='some' ref='#{other_relation.id}'/>
356           </relation>
357          </modify>
358         </osmChange>
359       CHANGESET
360
361       # upload it
362       post :upload, :params => { :id => changeset_id }, :body => diff
363       assert_response :success,
364                       "can't upload a simple valid diff to changeset: #{@response.body}"
365
366       # check that the changes made it into the database
367       assert_equal 0, Node.find(node.id).tags.size, "node #{node.id} should now have no tags"
368       assert_equal 0, Way.find(way.id).tags.size, "way #{way.id} should now have no tags"
369       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
370     end
371
372     ##
373     # upload something which creates new objects using placeholders
374     def test_upload_create_valid
375       user = create(:user)
376       changeset = create(:changeset, :user => user)
377       node = create(:node)
378       way = create(:way_with_nodes, :nodes_count => 2)
379       relation = create(:relation)
380
381       basic_authorization user.email, "test"
382
383       # simple diff to create a node way and relation using placeholders
384       diff = <<~CHANGESET
385         <osmChange>
386          <create>
387           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
388            <tag k='foo' v='bar'/>
389            <tag k='baz' v='bat'/>
390           </node>
391           <way id='-1' changeset='#{changeset.id}'>
392            <nd ref='#{node.id}'/>
393           </way>
394          </create>
395          <create>
396           <relation id='-1' changeset='#{changeset.id}'>
397            <member type='way' role='some' ref='#{way.id}'/>
398            <member type='node' role='some' ref='#{node.id}'/>
399            <member type='relation' role='some' ref='#{relation.id}'/>
400           </relation>
401          </create>
402         </osmChange>
403       CHANGESET
404
405       # upload it
406       post :upload, :params => { :id => changeset.id }, :body => diff
407       assert_response :success,
408                       "can't upload a simple valid creation to changeset: #{@response.body}"
409
410       # check the returned payload
411       assert_select "diffResult[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
412       assert_select "diffResult>node", 1
413       assert_select "diffResult>way", 1
414       assert_select "diffResult>relation", 1
415
416       # inspect the response to find out what the new element IDs are
417       doc = XML::Parser.string(@response.body).parse
418       new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
419       new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
420       new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
421
422       # check the old IDs are all present and negative one
423       assert_equal(-1, doc.find("//diffResult/node").first["old_id"].to_i)
424       assert_equal(-1, doc.find("//diffResult/way").first["old_id"].to_i)
425       assert_equal(-1, doc.find("//diffResult/relation").first["old_id"].to_i)
426
427       # check the versions are present and equal one
428       assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
429       assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
430       assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
431
432       # check that the changes made it into the database
433       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
434       assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
435       assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
436     end
437
438     ##
439     # test a complex delete where we delete elements which rely on eachother
440     # in the same transaction.
441     def test_upload_delete
442       changeset = create(:changeset)
443       super_relation = create(:relation)
444       used_relation = create(:relation)
445       used_way = create(:way)
446       used_node = create(:node)
447       create(:relation_member, :relation => super_relation, :member => used_relation)
448       create(:relation_member, :relation => super_relation, :member => used_way)
449       create(:relation_member, :relation => super_relation, :member => used_node)
450
451       basic_authorization changeset.user.display_name, "test"
452
453       diff = XML::Document.new
454       diff.root = XML::Node.new "osmChange"
455       delete = XML::Node.new "delete"
456       diff.root << delete
457       delete << xml_node_for_relation(super_relation)
458       delete << xml_node_for_relation(used_relation)
459       delete << xml_node_for_way(used_way)
460       delete << xml_node_for_node(used_node)
461
462       # update the changeset to one that this user owns
463       %w[node way relation].each do |type|
464         delete.find("//osmChange/delete/#{type}").each do |n|
465           n["changeset"] = changeset.id.to_s
466         end
467       end
468
469       # upload it
470       post :upload, :params => { :id => changeset.id }, :body => diff.to_s
471       assert_response :success,
472                       "can't upload a deletion diff to changeset: #{@response.body}"
473
474       # check the response is well-formed
475       assert_select "diffResult>node", 1
476       assert_select "diffResult>way", 1
477       assert_select "diffResult>relation", 2
478
479       # check that everything was deleted
480       assert_equal false, Node.find(used_node.id).visible
481       assert_equal false, Way.find(used_way.id).visible
482       assert_equal false, Relation.find(super_relation.id).visible
483       assert_equal false, Relation.find(used_relation.id).visible
484     end
485
486     ##
487     # test uploading a delete with no lat/lon, as they are optional in
488     # the osmChange spec.
489     def test_upload_nolatlon_delete
490       node = create(:node)
491       changeset = create(:changeset)
492
493       basic_authorization changeset.user.display_name, "test"
494       diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
495
496       # upload it
497       post :upload, :params => { :id => changeset.id }, :body => diff
498       assert_response :success,
499                       "can't upload a deletion diff to changeset: #{@response.body}"
500
501       # check the response is well-formed
502       assert_select "diffResult>node", 1
503
504       # check that everything was deleted
505       assert_equal false, Node.find(node.id).visible
506     end
507
508     def test_repeated_changeset_create
509       3.times do
510         basic_authorization create(:user).email, "test"
511
512         # create a temporary changeset
513         xml = "<osm><changeset>" \
514               "<tag k='created_by' v='osm test suite checking changesets'/>" \
515               "</changeset></osm>"
516         assert_difference "Changeset.count", 1 do
517           put :create, :body => xml
518         end
519         assert_response :success
520       end
521     end
522
523     def test_upload_large_changeset
524       basic_authorization create(:user).email, "test"
525
526       # create a changeset
527       put :create, :body => "<osm><changeset/></osm>"
528       assert_response :success, "Should be able to create a changeset: #{@response.body}"
529       changeset_id = @response.body.to_i
530
531       # upload some widely-spaced nodes, spiralling positive and negative
532       diff = <<~CHANGESET
533         <osmChange>
534          <create>
535           <node id='-1' lon='-20' lat='-10' changeset='#{changeset_id}'/>
536           <node id='-10' lon='20'  lat='10' changeset='#{changeset_id}'/>
537           <node id='-2' lon='-40' lat='-20' changeset='#{changeset_id}'/>
538           <node id='-11' lon='40'  lat='20' changeset='#{changeset_id}'/>
539           <node id='-3' lon='-60' lat='-30' changeset='#{changeset_id}'/>
540           <node id='-12' lon='60'  lat='30' changeset='#{changeset_id}'/>
541           <node id='-4' lon='-80' lat='-40' changeset='#{changeset_id}'/>
542           <node id='-13' lon='80'  lat='40' changeset='#{changeset_id}'/>
543           <node id='-5' lon='-100' lat='-50' changeset='#{changeset_id}'/>
544           <node id='-14' lon='100'  lat='50' changeset='#{changeset_id}'/>
545           <node id='-6' lon='-120' lat='-60' changeset='#{changeset_id}'/>
546           <node id='-15' lon='120'  lat='60' changeset='#{changeset_id}'/>
547           <node id='-7' lon='-140' lat='-70' changeset='#{changeset_id}'/>
548           <node id='-16' lon='140'  lat='70' changeset='#{changeset_id}'/>
549           <node id='-8' lon='-160' lat='-80' changeset='#{changeset_id}'/>
550           <node id='-17' lon='160'  lat='80' changeset='#{changeset_id}'/>
551           <node id='-9' lon='-179.9' lat='-89.9' changeset='#{changeset_id}'/>
552           <node id='-18' lon='179.9'  lat='89.9' changeset='#{changeset_id}'/>
553          </create>
554         </osmChange>
555       CHANGESET
556
557       # upload it, which used to cause an error like "PGError: ERROR:
558       # integer out of range" (bug #2152). but shouldn't any more.
559       post :upload, :params => { :id => changeset_id }, :body => diff
560       assert_response :success,
561                       "can't upload a spatially-large diff to changeset: #{@response.body}"
562
563       # check that the changeset bbox is within bounds
564       cs = Changeset.find(changeset_id)
565       assert cs.min_lon >= -180 * GeoRecord::SCALE, "Minimum longitude (#{cs.min_lon / GeoRecord::SCALE}) should be >= -180 to be valid."
566       assert cs.max_lon <= 180 * GeoRecord::SCALE, "Maximum longitude (#{cs.max_lon / GeoRecord::SCALE}) should be <= 180 to be valid."
567       assert cs.min_lat >= -90 * GeoRecord::SCALE, "Minimum latitude (#{cs.min_lat / GeoRecord::SCALE}) should be >= -90 to be valid."
568       assert cs.max_lat <= 90 * GeoRecord::SCALE, "Maximum latitude (#{cs.max_lat / GeoRecord::SCALE}) should be <= 90 to be valid."
569     end
570
571     ##
572     # test that deleting stuff in a transaction doesn't bypass the checks
573     # to ensure that used elements are not deleted.
574     def test_upload_delete_invalid
575       changeset = create(:changeset)
576       relation = create(:relation)
577       other_relation = create(:relation)
578       used_way = create(:way)
579       used_node = create(:node)
580       create(:relation_member, :relation => relation, :member => used_way)
581       create(:relation_member, :relation => relation, :member => used_node)
582
583       basic_authorization changeset.user.email, "test"
584
585       diff = XML::Document.new
586       diff.root = XML::Node.new "osmChange"
587       delete = XML::Node.new "delete"
588       diff.root << delete
589       delete << xml_node_for_relation(other_relation)
590       delete << xml_node_for_way(used_way)
591       delete << xml_node_for_node(used_node)
592
593       # update the changeset to one that this user owns
594       %w[node way relation].each do |type|
595         delete.find("//osmChange/delete/#{type}").each do |n|
596           n["changeset"] = changeset.id.to_s
597         end
598       end
599
600       # upload it
601       post :upload, :params => { :id => changeset.id }, :body => diff.to_s
602       assert_response :precondition_failed,
603                       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
604       assert_equal "Precondition failed: Way #{used_way.id} is still used by relations #{relation.id}.", @response.body
605
606       # check that nothing was, in fact, deleted
607       assert_equal true, Node.find(used_node.id).visible
608       assert_equal true, Way.find(used_way.id).visible
609       assert_equal true, Relation.find(relation.id).visible
610       assert_equal true, Relation.find(other_relation.id).visible
611     end
612
613     ##
614     # test that a conditional delete of an in use object works.
615     def test_upload_delete_if_unused
616       changeset = create(:changeset)
617       super_relation = create(:relation)
618       used_relation = create(:relation)
619       used_way = create(:way)
620       used_node = create(:node)
621       create(:relation_member, :relation => super_relation, :member => used_relation)
622       create(:relation_member, :relation => super_relation, :member => used_way)
623       create(:relation_member, :relation => super_relation, :member => used_node)
624
625       basic_authorization changeset.user.email, "test"
626
627       diff = XML::Document.new
628       diff.root = XML::Node.new "osmChange"
629       delete = XML::Node.new "delete"
630       diff.root << delete
631       delete["if-unused"] = ""
632       delete << xml_node_for_relation(used_relation)
633       delete << xml_node_for_way(used_way)
634       delete << xml_node_for_node(used_node)
635
636       # update the changeset to one that this user owns
637       %w[node way relation].each do |type|
638         delete.find("//osmChange/delete/#{type}").each do |n|
639           n["changeset"] = changeset.id.to_s
640         end
641       end
642
643       # upload it
644       post :upload, :params => { :id => changeset.id }, :body => diff.to_s
645       assert_response :success,
646                       "can't do a conditional delete of in use objects: #{@response.body}"
647
648       # check the returned payload
649       assert_select "diffResult[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
650       assert_select "diffResult>node", 1
651       assert_select "diffResult>way", 1
652       assert_select "diffResult>relation", 1
653
654       # parse the response
655       doc = XML::Parser.string(@response.body).parse
656
657       # check the old IDs are all present and what we expect
658       assert_equal used_node.id, doc.find("//diffResult/node").first["old_id"].to_i
659       assert_equal used_way.id, doc.find("//diffResult/way").first["old_id"].to_i
660       assert_equal used_relation.id, doc.find("//diffResult/relation").first["old_id"].to_i
661
662       # check the new IDs are all present and unchanged
663       assert_equal used_node.id, doc.find("//diffResult/node").first["new_id"].to_i
664       assert_equal used_way.id, doc.find("//diffResult/way").first["new_id"].to_i
665       assert_equal used_relation.id, doc.find("//diffResult/relation").first["new_id"].to_i
666
667       # check the new versions are all present and unchanged
668       assert_equal used_node.version, doc.find("//diffResult/node").first["new_version"].to_i
669       assert_equal used_way.version, doc.find("//diffResult/way").first["new_version"].to_i
670       assert_equal used_relation.version, doc.find("//diffResult/relation").first["new_version"].to_i
671
672       # check that nothing was, in fact, deleted
673       assert_equal true, Node.find(used_node.id).visible
674       assert_equal true, Way.find(used_way.id).visible
675       assert_equal true, Relation.find(used_relation.id).visible
676     end
677
678     ##
679     # upload an element with a really long tag value
680     def test_upload_invalid_too_long_tag
681       changeset = create(:changeset)
682
683       basic_authorization changeset.user.email, "test"
684
685       # simple diff to create a node way and relation using placeholders
686       diff = <<~CHANGESET
687         <osmChange>
688          <create>
689           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
690            <tag k='foo' v='#{'x' * 256}'/>
691           </node>
692          </create>
693         </osmChange>
694       CHANGESET
695
696       # upload it
697       post :upload, :params => { :id => changeset.id }, :body => diff
698       assert_response :bad_request,
699                       "shoudln't be able to upload too long a tag to changeset: #{@response.body}"
700     end
701
702     ##
703     # upload something which creates new objects and inserts them into
704     # existing containers using placeholders.
705     def test_upload_complex
706       way = create(:way)
707       node = create(:node)
708       relation = create(:relation)
709       create(:way_node, :way => way, :node => node)
710
711       changeset = create(:changeset)
712
713       basic_authorization changeset.user.email, "test"
714
715       # simple diff to create a node way and relation using placeholders
716       diff = <<~CHANGESET
717         <osmChange>
718          <create>
719           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
720            <tag k='foo' v='bar'/>
721            <tag k='baz' v='bat'/>
722           </node>
723          </create>
724          <modify>
725           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
726            <nd ref='-1'/>
727            <nd ref='#{node.id}'/>
728           </way>
729           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
730            <member type='way' role='some' ref='#{way.id}'/>
731            <member type='node' role='some' ref='-1'/>
732            <member type='relation' role='some' ref='#{relation.id}'/>
733           </relation>
734          </modify>
735         </osmChange>
736       CHANGESET
737
738       # upload it
739       post :upload, :params => { :id => changeset.id }, :body => diff
740       assert_response :success,
741                       "can't upload a complex diff to changeset: #{@response.body}"
742
743       # check the returned payload
744       assert_select "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
745       assert_select "diffResult>node", 1
746       assert_select "diffResult>way", 1
747       assert_select "diffResult>relation", 1
748
749       # inspect the response to find out what the new element IDs are
750       doc = XML::Parser.string(@response.body).parse
751       new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
752
753       # check that the changes made it into the database
754       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
755       assert_equal [new_node_id, node.id], Way.find(way.id).nds, "way nodes should match"
756       Relation.find(relation.id).members.each do |type, id, _role|
757         assert_equal new_node_id, id, "relation should contain new node" if type == "node"
758       end
759     end
760
761     ##
762     # create a diff which references several changesets, which should cause
763     # a rollback and none of the diff gets committed
764     def test_upload_invalid_changesets
765       changeset = create(:changeset)
766       other_changeset = create(:changeset, :user => changeset.user)
767       node = create(:node)
768       way = create(:way)
769       relation = create(:relation)
770       other_relation = create(:relation)
771
772       basic_authorization changeset.user.email, "test"
773
774       # simple diff to create a node way and relation using placeholders
775       diff = <<~CHANGESET
776         <osmChange>
777          <modify>
778           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
779           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
780            <nd ref='#{node.id}'/>
781           </way>
782          </modify>
783          <modify>
784           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
785            <member type='way' role='some' ref='#{way.id}'/>
786            <member type='node' role='some' ref='#{node.id}'/>
787            <member type='relation' role='some' ref='#{other_relation.id}'/>
788           </relation>
789          </modify>
790          <create>
791           <node id='-1' lon='0' lat='0' changeset='#{other_changeset.id}'>
792            <tag k='foo' v='bar'/>
793            <tag k='baz' v='bat'/>
794           </node>
795          </create>
796         </osmChange>
797       CHANGESET
798
799       # upload it
800       post :upload, :params => { :id => changeset.id }, :body => diff
801       assert_response :conflict,
802                       "uploading a diff with multiple changesets should have failed"
803
804       # check that objects are unmodified
805       assert_nodes_are_equal(node, Node.find(node.id))
806       assert_ways_are_equal(way, Way.find(way.id))
807       assert_relations_are_equal(relation, Relation.find(relation.id))
808     end
809
810     ##
811     # upload multiple versions of the same element in the same diff.
812     def test_upload_multiple_valid
813       node = create(:node)
814       changeset = create(:changeset)
815       basic_authorization changeset.user.email, "test"
816
817       # change the location of a node multiple times, each time referencing
818       # the last version. doesn't this depend on version numbers being
819       # sequential?
820       diff = <<~CHANGESET
821         <osmChange>
822          <modify>
823           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
824           <node id='#{node.id}' lon='1' lat='0' changeset='#{changeset.id}' version='2'/>
825           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='3'/>
826           <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='4'/>
827           <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset.id}' version='5'/>
828           <node id='#{node.id}' lon='3' lat='2' changeset='#{changeset.id}' version='6'/>
829           <node id='#{node.id}' lon='3' lat='3' changeset='#{changeset.id}' version='7'/>
830           <node id='#{node.id}' lon='9' lat='9' changeset='#{changeset.id}' version='8'/>
831          </modify>
832         </osmChange>
833       CHANGESET
834
835       # upload it
836       post :upload, :params => { :id => changeset.id }, :body => diff
837       assert_response :success,
838                       "can't upload multiple versions of an element in a diff: #{@response.body}"
839
840       # check the response is well-formed. its counter-intuitive, but the
841       # API will return multiple elements with the same ID and different
842       # version numbers for each change we made.
843       assert_select "diffResult>node", 8
844     end
845
846     ##
847     # upload multiple versions of the same element in the same diff, but
848     # keep the version numbers the same.
849     def test_upload_multiple_duplicate
850       node = create(:node)
851       changeset = create(:changeset)
852
853       basic_authorization changeset.user.email, "test"
854
855       diff = <<~CHANGESET
856         <osmChange>
857          <modify>
858           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
859           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
860          </modify>
861         </osmChange>
862       CHANGESET
863
864       # upload it
865       post :upload, :params => { :id => changeset.id }, :body => diff
866       assert_response :conflict,
867                       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
868     end
869
870     ##
871     # try to upload some elements without specifying the version
872     def test_upload_missing_version
873       changeset = create(:changeset)
874
875       basic_authorization changeset.user.email, "test"
876
877       diff = <<~CHANGESET
878         <osmChange>
879          <modify>
880          <node id='1' lon='1' lat='1' changeset='#{changeset.id}'/>
881          </modify>
882         </osmChange>
883       CHANGESET
884
885       # upload it
886       post :upload, :params => { :id => changeset.id }, :body => diff
887       assert_response :bad_request,
888                       "shouldn't be able to upload an element without version: #{@response.body}"
889     end
890
891     ##
892     # try to upload with commands other than create, modify, or delete
893     def test_action_upload_invalid
894       changeset = create(:changeset)
895
896       basic_authorization changeset.user.email, "test"
897
898       diff = <<~CHANGESET
899         <osmChange>
900           <ping>
901            <node id='1' lon='1' lat='1' changeset='#{changeset.id}' />
902           </ping>
903         </osmChange>
904       CHANGESET
905       post :upload, :params => { :id => changeset.id }, :body => diff
906       assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
907       assert_equal @response.body, "Unknown action ping, choices are create, modify, delete"
908     end
909
910     ##
911     # upload a valid changeset which has a mixture of whitespace
912     # to check a bug reported by ivansanchez (#1565).
913     def test_upload_whitespace_valid
914       changeset = create(:changeset)
915       node = create(:node)
916       way = create(:way_with_nodes, :nodes_count => 2)
917       relation = create(:relation)
918       other_relation = create(:relation)
919       create(:relation_tag, :relation => relation)
920
921       basic_authorization changeset.user.email, "test"
922
923       diff = <<~CHANGESET
924         <osmChange>
925          <modify><node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}'
926           version='1'></node>
927           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='2'><tag k='k' v='v'/></node></modify>
928          <modify>
929          <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'><member
930            type='way' role='some' ref='#{way.id}'/><member
931             type='node' role='some' ref='#{node.id}'/>
932            <member type='relation' role='some' ref='#{other_relation.id}'/>
933           </relation>
934          </modify></osmChange>
935       CHANGESET
936
937       # upload it
938       post :upload, :params => { :id => changeset.id }, :body => diff
939       assert_response :success,
940                       "can't upload a valid diff with whitespace variations to changeset: #{@response.body}"
941
942       # check the response is well-formed
943       assert_select "diffResult>node", 2
944       assert_select "diffResult>relation", 1
945
946       # check that the changes made it into the database
947       assert_equal 1, Node.find(node.id).tags.size, "node #{node.id} should now have one tag"
948       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
949     end
950
951     ##
952     # test that a placeholder can be reused within the same upload.
953     def test_upload_reuse_placeholder_valid
954       changeset = create(:changeset)
955
956       basic_authorization changeset.user.email, "test"
957
958       diff = <<~CHANGESET
959         <osmChange>
960          <create>
961           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
962            <tag k="foo" v="bar"/>
963           </node>
964          </create>
965          <modify>
966           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
967          </modify>
968          <delete>
969           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
970          </delete>
971         </osmChange>
972       CHANGESET
973
974       # upload it
975       post :upload, :params => { :id => changeset.id }, :body => diff
976       assert_response :success,
977                       "can't upload a valid diff with re-used placeholders to changeset: #{@response.body}"
978
979       # check the response is well-formed
980       assert_select "diffResult>node", 3
981       assert_select "diffResult>node[old_id='-1']", 3
982     end
983
984     ##
985     # test what happens if a diff upload re-uses placeholder IDs in an
986     # illegal way.
987     def test_upload_placeholder_invalid
988       changeset = create(:changeset)
989
990       basic_authorization changeset.user.email, "test"
991
992       diff = <<~CHANGESET
993         <osmChange>
994          <create>
995           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
996           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
997           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
998          </create>
999         </osmChange>
1000       CHANGESET
1001
1002       # upload it
1003       post :upload, :params => { :id => changeset.id }, :body => diff
1004       assert_response :bad_request,
1005                       "shouldn't be able to re-use placeholder IDs"
1006     end
1007
1008     ##
1009     # test that uploading a way referencing invalid placeholders gives a
1010     # proper error, not a 500.
1011     def test_upload_placeholder_invalid_way
1012       changeset = create(:changeset)
1013       way = create(:way)
1014
1015       basic_authorization changeset.user.email, "test"
1016
1017       diff = <<~CHANGESET
1018         <osmChange>
1019          <create>
1020           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1021           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1022           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1023           <way id="-1" changeset="#{changeset.id}" version="1">
1024            <nd ref="-1"/>
1025            <nd ref="-2"/>
1026            <nd ref="-3"/>
1027            <nd ref="-4"/>
1028           </way>
1029          </create>
1030         </osmChange>
1031       CHANGESET
1032
1033       # upload it
1034       post :upload, :params => { :id => changeset.id }, :body => diff
1035       assert_response :bad_request,
1036                       "shouldn't be able to use invalid placeholder IDs"
1037       assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
1038
1039       # the same again, but this time use an existing way
1040       diff = <<~CHANGESET
1041         <osmChange>
1042          <create>
1043           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1044           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1045           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1046           <way id="#{way.id}" changeset="#{changeset.id}" version="1">
1047            <nd ref="-1"/>
1048            <nd ref="-2"/>
1049            <nd ref="-3"/>
1050            <nd ref="-4"/>
1051           </way>
1052          </create>
1053         </osmChange>
1054       CHANGESET
1055
1056       # upload it
1057       post :upload, :params => { :id => changeset.id }, :body => diff
1058       assert_response :bad_request,
1059                       "shouldn't be able to use invalid placeholder IDs"
1060       assert_equal "Placeholder node not found for reference -4 in way #{way.id}", @response.body
1061     end
1062
1063     ##
1064     # test that uploading a relation referencing invalid placeholders gives a
1065     # proper error, not a 500.
1066     def test_upload_placeholder_invalid_relation
1067       changeset = create(:changeset)
1068       relation = create(:relation)
1069
1070       basic_authorization changeset.user.email, "test"
1071
1072       diff = <<~CHANGESET
1073         <osmChange>
1074          <create>
1075           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1076           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1077           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1078           <relation id="-1" changeset="#{changeset.id}" version="1">
1079            <member type="node" role="foo" ref="-1"/>
1080            <member type="node" role="foo" ref="-2"/>
1081            <member type="node" role="foo" ref="-3"/>
1082            <member type="node" role="foo" ref="-4"/>
1083           </relation>
1084          </create>
1085         </osmChange>
1086       CHANGESET
1087
1088       # upload it
1089       post :upload, :params => { :id => changeset.id }, :body => diff
1090       assert_response :bad_request,
1091                       "shouldn't be able to use invalid placeholder IDs"
1092       assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
1093
1094       # the same again, but this time use an existing relation
1095       diff = <<~CHANGESET
1096         <osmChange>
1097          <create>
1098           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1099           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1100           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1101           <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
1102            <member type="node" role="foo" ref="-1"/>
1103            <member type="node" role="foo" ref="-2"/>
1104            <member type="node" role="foo" ref="-3"/>
1105            <member type="way" role="bar" ref="-1"/>
1106           </relation>
1107          </create>
1108         </osmChange>
1109       CHANGESET
1110
1111       # upload it
1112       post :upload, :params => { :id => changeset.id }, :body => diff
1113       assert_response :bad_request,
1114                       "shouldn't be able to use invalid placeholder IDs"
1115       assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
1116     end
1117
1118     ##
1119     # test what happens if a diff is uploaded containing only a node
1120     # move.
1121     def test_upload_node_move
1122       basic_authorization create(:user).email, "test"
1123
1124       xml = "<osm><changeset>" \
1125             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1126             "</changeset></osm>"
1127       put :create, :body => xml
1128       assert_response :success
1129       changeset_id = @response.body.to_i
1130
1131       old_node = create(:node, :lat => 1, :lon => 1)
1132
1133       diff = XML::Document.new
1134       diff.root = XML::Node.new "osmChange"
1135       modify = XML::Node.new "modify"
1136       xml_old_node = xml_node_for_node(old_node)
1137       xml_old_node["lat"] = 2.0.to_s
1138       xml_old_node["lon"] = 2.0.to_s
1139       xml_old_node["changeset"] = changeset_id.to_s
1140       modify << xml_old_node
1141       diff.root << modify
1142
1143       # upload it
1144       post :upload, :params => { :id => changeset_id }, :body => diff.to_s
1145       assert_response :success,
1146                       "diff should have uploaded OK"
1147
1148       # check the bbox
1149       changeset = Changeset.find(changeset_id)
1150       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1151       assert_equal 2 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 2 degrees"
1152       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1153       assert_equal 2 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 2 degrees"
1154     end
1155
1156     ##
1157     # test what happens if a diff is uploaded adding a node to a way.
1158     def test_upload_way_extend
1159       basic_authorization create(:user).email, "test"
1160
1161       xml = "<osm><changeset>" \
1162             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1163             "</changeset></osm>"
1164       put :create, :body => xml
1165       assert_response :success
1166       changeset_id = @response.body.to_i
1167
1168       old_way = create(:way)
1169       create(:way_node, :way => old_way, :node => create(:node, :lat => 1, :lon => 1))
1170
1171       diff = XML::Document.new
1172       diff.root = XML::Node.new "osmChange"
1173       modify = XML::Node.new "modify"
1174       xml_old_way = xml_node_for_way(old_way)
1175       nd_ref = XML::Node.new "nd"
1176       nd_ref["ref"] = create(:node, :lat => 3, :lon => 3).id.to_s
1177       xml_old_way << nd_ref
1178       xml_old_way["changeset"] = changeset_id.to_s
1179       modify << xml_old_way
1180       diff.root << modify
1181
1182       # upload it
1183       post :upload, :params => { :id => changeset_id }, :body => diff.to_s
1184       assert_response :success,
1185                       "diff should have uploaded OK"
1186
1187       # check the bbox
1188       changeset = Changeset.find(changeset_id)
1189       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1190       assert_equal 3 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 3 degrees"
1191       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1192       assert_equal 3 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 3 degrees"
1193     end
1194
1195     ##
1196     # test for more issues in #1568
1197     def test_upload_empty_invalid
1198       changeset = create(:changeset)
1199
1200       basic_authorization changeset.user.email, "test"
1201
1202       ["<osmChange/>",
1203        "<osmChange></osmChange>",
1204        "<osmChange><modify/></osmChange>",
1205        "<osmChange><modify></modify></osmChange>"].each do |diff|
1206         # upload it
1207         post :upload, :params => { :id => changeset.id }, :body => diff
1208         assert_response(:success, "should be able to upload " \
1209                         "empty changeset: " + diff)
1210       end
1211     end
1212
1213     ##
1214     # test that the X-Error-Format header works to request XML errors
1215     def test_upload_xml_errors
1216       changeset = create(:changeset)
1217       node = create(:node)
1218       create(:relation_member, :member => node)
1219
1220       basic_authorization changeset.user.email, "test"
1221
1222       # try and delete a node that is in use
1223       diff = XML::Document.new
1224       diff.root = XML::Node.new "osmChange"
1225       delete = XML::Node.new "delete"
1226       diff.root << delete
1227       delete << xml_node_for_node(node)
1228
1229       # upload it
1230       error_format "xml"
1231       post :upload, :params => { :id => changeset.id }, :body => diff.to_s
1232       assert_response :success,
1233                       "failed to return error in XML format"
1234
1235       # check the returned payload
1236       assert_select "osmError[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
1237       assert_select "osmError>status", 1
1238       assert_select "osmError>message", 1
1239     end
1240
1241     ##
1242     # when we make some simple changes we get the same changes back from the
1243     # diff download.
1244     def test_diff_download_simple
1245       node = create(:node)
1246
1247       ## First try with a non-public user, which should get a forbidden
1248       basic_authorization create(:user, :data_public => false).email, "test"
1249
1250       # create a temporary changeset
1251       xml = "<osm><changeset>" \
1252             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1253             "</changeset></osm>"
1254       put :create, :body => xml
1255       assert_response :forbidden
1256
1257       ## Now try with a normal user
1258       basic_authorization create(:user).email, "test"
1259
1260       # create a temporary changeset
1261       xml = "<osm><changeset>" \
1262             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1263             "</changeset></osm>"
1264       put :create, :body => xml
1265       assert_response :success
1266       changeset_id = @response.body.to_i
1267
1268       # add a diff to it
1269       diff = <<~CHANGESET
1270         <osmChange>
1271          <modify>
1272           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1273           <node id='#{node.id}' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
1274           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
1275           <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
1276           <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
1277           <node id='#{node.id}' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
1278           <node id='#{node.id}' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
1279           <node id='#{node.id}' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
1280          </modify>
1281         </osmChange>
1282       CHANGESET
1283
1284       # upload it
1285       post :upload, :params => { :id => changeset_id }, :body => diff
1286       assert_response :success,
1287                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1288
1289       get :download, :params => { :id => changeset_id }
1290       assert_response :success
1291
1292       assert_select "osmChange", 1
1293       assert_select "osmChange>modify", 8
1294       assert_select "osmChange>modify>node", 8
1295     end
1296
1297     ##
1298     # culled this from josm to ensure that nothing in the way that josm
1299     # is formatting the request is causing it to fail.
1300     #
1301     # NOTE: the error turned out to be something else completely!
1302     def test_josm_upload
1303       basic_authorization create(:user).email, "test"
1304
1305       # create a temporary changeset
1306       xml = "<osm><changeset>" \
1307             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1308             "</changeset></osm>"
1309       put :create, :body => xml
1310       assert_response :success
1311       changeset_id = @response.body.to_i
1312
1313       diff = <<~OSMFILE
1314         <osmChange version="0.6" generator="JOSM">
1315         <create version="0.6" generator="JOSM">
1316           <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
1317           <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
1318           <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
1319           <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
1320           <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
1321           <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
1322           <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
1323           <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
1324           <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
1325           <way id='-10' action='modiy' visible='true' changeset='#{changeset_id}'>
1326             <nd ref='-1' />
1327             <nd ref='-2' />
1328             <nd ref='-3' />
1329             <nd ref='-4' />
1330             <nd ref='-5' />
1331             <nd ref='-6' />
1332             <nd ref='-7' />
1333             <nd ref='-8' />
1334             <nd ref='-9' />
1335             <tag k='highway' v='residential' />
1336             <tag k='name' v='Foobar Street' />
1337           </way>
1338         </create>
1339         </osmChange>
1340       OSMFILE
1341
1342       # upload it
1343       post :upload, :params => { :id => changeset_id }, :body => diff
1344       assert_response :success,
1345                       "can't upload a diff from JOSM: #{@response.body}"
1346
1347       get :download, :params => { :id => changeset_id }
1348       assert_response :success
1349
1350       assert_select "osmChange", 1
1351       assert_select "osmChange>create>node", 9
1352       assert_select "osmChange>create>way", 1
1353       assert_select "osmChange>create>way>nd", 9
1354       assert_select "osmChange>create>way>tag", 2
1355     end
1356
1357     ##
1358     # when we make some complex changes we get the same changes back from the
1359     # diff download.
1360     def test_diff_download_complex
1361       node = create(:node)
1362       node2 = create(:node)
1363       way = create(:way)
1364       basic_authorization create(:user).email, "test"
1365
1366       # create a temporary changeset
1367       xml = "<osm><changeset>" \
1368             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1369             "</changeset></osm>"
1370       put :create, :body => xml
1371       assert_response :success
1372       changeset_id = @response.body.to_i
1373
1374       # add a diff to it
1375       diff = <<~CHANGESET
1376         <osmChange>
1377          <delete>
1378           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1379          </delete>
1380          <create>
1381           <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
1382           <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
1383           <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
1384          </create>
1385          <modify>
1386           <node id='#{node2.id}' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
1387           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
1388            <nd ref='#{node2.id}'/>
1389            <nd ref='-1'/>
1390            <nd ref='-2'/>
1391            <nd ref='-3'/>
1392           </way>
1393          </modify>
1394         </osmChange>
1395       CHANGESET
1396
1397       # upload it
1398       post :upload, :params => { :id => changeset_id }, :body => diff
1399       assert_response :success,
1400                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1401
1402       get :download, :params => { :id => changeset_id }
1403       assert_response :success
1404
1405       assert_select "osmChange", 1
1406       assert_select "osmChange>create", 3
1407       assert_select "osmChange>delete", 1
1408       assert_select "osmChange>modify", 2
1409       assert_select "osmChange>create>node", 3
1410       assert_select "osmChange>delete>node", 1
1411       assert_select "osmChange>modify>node", 1
1412       assert_select "osmChange>modify>way", 1
1413     end
1414
1415     def test_changeset_download
1416       changeset = create(:changeset)
1417       node = create(:node, :with_history, :version => 1, :changeset => changeset)
1418       tag = create(:old_node_tag, :old_node => node.old_nodes.find_by(:version => 1))
1419       node2 = create(:node, :with_history, :version => 1, :changeset => changeset)
1420       _node3 = create(:node, :with_history, :deleted, :version => 1, :changeset => changeset)
1421       _relation = create(:relation, :with_history, :version => 1, :changeset => changeset)
1422       _relation2 = create(:relation, :with_history, :deleted, :version => 1, :changeset => changeset)
1423
1424       get :download, :params => { :id => changeset.id }
1425
1426       assert_response :success
1427       assert_template nil
1428       # print @response.body
1429       # FIXME: needs more assert_select tests
1430       assert_select "osmChange[version='#{Settings.api_version}'][generator='#{Settings.generator}']" do
1431         assert_select "create", :count => 5
1432         assert_select "create>node[id='#{node.id}'][visible='#{node.visible?}'][version='#{node.version}']" do
1433           assert_select "tag[k='#{tag.k}'][v='#{tag.v}']"
1434         end
1435         assert_select "create>node[id='#{node2.id}']"
1436       end
1437     end
1438
1439     ##
1440     # check that the bounding box of a changeset gets updated correctly
1441     # FIXME: This should really be moded to a integration test due to the with_controller
1442     def test_changeset_bbox
1443       way = create(:way)
1444       create(:way_node, :way => way, :node => create(:node, :lat => 3, :lon => 3))
1445
1446       basic_authorization create(:user).email, "test"
1447
1448       # create a new changeset
1449       xml = "<osm><changeset/></osm>"
1450       put :create, :body => xml
1451       assert_response :success, "Creating of changeset failed."
1452       changeset_id = @response.body.to_i
1453
1454       # add a single node to it
1455       with_controller(NodesController.new) do
1456         xml = "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
1457         put :create, :body => xml
1458         assert_response :success, "Couldn't create node."
1459       end
1460
1461       # get the bounding box back from the changeset
1462       get :show, :params => { :id => changeset_id }
1463       assert_response :success, "Couldn't read back changeset."
1464       assert_select "osm>changeset[min_lon='1.0000000']", 1
1465       assert_select "osm>changeset[max_lon='1.0000000']", 1
1466       assert_select "osm>changeset[min_lat='2.0000000']", 1
1467       assert_select "osm>changeset[max_lat='2.0000000']", 1
1468
1469       # add another node to it
1470       with_controller(NodesController.new) do
1471         xml = "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
1472         put :create, :body => xml
1473         assert_response :success, "Couldn't create second node."
1474       end
1475
1476       # get the bounding box back from the changeset
1477       get :show, :params => { :id => changeset_id }
1478       assert_response :success, "Couldn't read back changeset for the second time."
1479       assert_select "osm>changeset[min_lon='1.0000000']", 1
1480       assert_select "osm>changeset[max_lon='2.0000000']", 1
1481       assert_select "osm>changeset[min_lat='1.0000000']", 1
1482       assert_select "osm>changeset[max_lat='2.0000000']", 1
1483
1484       # add (delete) a way to it, which contains a point at (3,3)
1485       with_controller(WaysController.new) do
1486         xml = update_changeset(xml_for_way(way), changeset_id)
1487         put :delete, :params => { :id => way.id }, :body => xml.to_s
1488         assert_response :success, "Couldn't delete a way."
1489       end
1490
1491       # get the bounding box back from the changeset
1492       get :show, :params => { :id => changeset_id }
1493       assert_response :success, "Couldn't read back changeset for the third time."
1494       assert_select "osm>changeset[min_lon='1.0000000']", 1
1495       assert_select "osm>changeset[max_lon='3.0000000']", 1
1496       assert_select "osm>changeset[min_lat='1.0000000']", 1
1497       assert_select "osm>changeset[max_lat='3.0000000']", 1
1498     end
1499
1500     ##
1501     # test the query functionality of changesets
1502     def test_query
1503       private_user = create(:user, :data_public => false)
1504       private_user_changeset = create(:changeset, :user => private_user)
1505       private_user_closed_changeset = create(:changeset, :closed, :user => private_user)
1506       user = create(:user)
1507       changeset = create(:changeset, :user => user)
1508       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))
1509       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)
1510       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)
1511
1512       get :query, :params => { :bbox => "-10,-10, 10, 10" }
1513       assert_response :success, "can't get changesets in bbox"
1514       assert_changesets [changeset2, changeset3]
1515
1516       get :query, :params => { :bbox => "4.5,4.5,4.6,4.6" }
1517       assert_response :success, "can't get changesets in bbox"
1518       assert_changesets [changeset3]
1519
1520       # not found when looking for changesets of non-existing users
1521       get :query, :params => { :user => User.maximum(:id) + 1 }
1522       assert_response :not_found
1523       get :query, :params => { :display_name => " " }
1524       assert_response :not_found
1525
1526       # can't get changesets of user 1 without authenticating
1527       get :query, :params => { :user => private_user.id }
1528       assert_response :not_found, "shouldn't be able to get changesets by non-public user (ID)"
1529       get :query, :params => { :display_name => private_user.display_name }
1530       assert_response :not_found, "shouldn't be able to get changesets by non-public user (name)"
1531
1532       # but this should work
1533       basic_authorization private_user.email, "test"
1534       get :query, :params => { :user => private_user.id }
1535       assert_response :success, "can't get changesets by user ID"
1536       assert_changesets [private_user_changeset, private_user_closed_changeset]
1537
1538       get :query, :params => { :display_name => private_user.display_name }
1539       assert_response :success, "can't get changesets by user name"
1540       assert_changesets [private_user_changeset, private_user_closed_changeset]
1541
1542       # check that the correct error is given when we provide both UID and name
1543       get :query, :params => { :user => private_user.id,
1544                                :display_name => private_user.display_name }
1545       assert_response :bad_request, "should be a bad request to have both ID and name specified"
1546
1547       get :query, :params => { :user => private_user.id, :open => true }
1548       assert_response :success, "can't get changesets by user and open"
1549       assert_changesets [private_user_changeset]
1550
1551       get :query, :params => { :time => "2007-12-31" }
1552       assert_response :success, "can't get changesets by time-since"
1553       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1554
1555       get :query, :params => { :time => "2008-01-01T12:34Z" }
1556       assert_response :success, "can't get changesets by time-since with hour"
1557       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1558
1559       get :query, :params => { :time => "2007-12-31T23:59Z,2008-01-02T00:01Z" }
1560       assert_response :success, "can't get changesets by time-range"
1561       assert_changesets [closed_changeset]
1562
1563       get :query, :params => { :open => "true" }
1564       assert_response :success, "can't get changesets by open-ness"
1565       assert_changesets [private_user_changeset, changeset, changeset2, changeset3]
1566
1567       get :query, :params => { :closed => "true" }
1568       assert_response :success, "can't get changesets by closed-ness"
1569       assert_changesets [private_user_closed_changeset, closed_changeset]
1570
1571       get :query, :params => { :closed => "true", :user => private_user.id }
1572       assert_response :success, "can't get changesets by closed-ness and user"
1573       assert_changesets [private_user_closed_changeset]
1574
1575       get :query, :params => { :closed => "true", :user => user.id }
1576       assert_response :success, "can't get changesets by closed-ness and user"
1577       assert_changesets [closed_changeset]
1578
1579       get :query, :params => { :changesets => "#{private_user_changeset.id},#{changeset.id},#{closed_changeset.id}" }
1580       assert_response :success, "can't get changesets by id (as comma-separated string)"
1581       assert_changesets [private_user_changeset, changeset, closed_changeset]
1582
1583       get :query, :params => { :changesets => "" }
1584       assert_response :bad_request, "should be a bad request since changesets is empty"
1585     end
1586
1587     ##
1588     # check that errors are returned if garbage is inserted
1589     # into query strings
1590     def test_query_invalid
1591       ["abracadabra!",
1592        "1,2,3,F",
1593        ";drop table users;"].each do |bbox|
1594         get :query, :params => { :bbox => bbox }
1595         assert_response :bad_request, "'#{bbox}' isn't a bbox"
1596       end
1597
1598       ["now()",
1599        "00-00-00",
1600        ";drop table users;",
1601        ",",
1602        "-,-"].each do |time|
1603         get :query, :params => { :time => time }
1604         assert_response :bad_request, "'#{time}' isn't a valid time range"
1605       end
1606
1607       ["me",
1608        "foobar",
1609        "-1",
1610        "0"].each do |uid|
1611         get :query, :params => { :user => uid }
1612         assert_response :bad_request, "'#{uid}' isn't a valid user ID"
1613       end
1614     end
1615
1616     ##
1617     # check updating tags on a changeset
1618     def test_changeset_update
1619       private_user = create(:user, :data_public => false)
1620       private_changeset = create(:changeset, :user => private_user)
1621       user = create(:user)
1622       changeset = create(:changeset, :user => user)
1623
1624       ## First try with a non-public user
1625       new_changeset = create_changeset_xml(:user => private_user)
1626       new_tag = XML::Node.new "tag"
1627       new_tag["k"] = "tagtesting"
1628       new_tag["v"] = "valuetesting"
1629       new_changeset.find("//osm/changeset").first << new_tag
1630
1631       # try without any authorization
1632       put :update, :params => { :id => private_changeset.id }, :body => new_changeset.to_s
1633       assert_response :unauthorized
1634
1635       # try with the wrong authorization
1636       basic_authorization create(:user).email, "test"
1637       put :update, :params => { :id => private_changeset.id }, :body => new_changeset.to_s
1638       assert_response :conflict
1639
1640       # now this should get an unauthorized
1641       basic_authorization private_user.email, "test"
1642       put :update, :params => { :id => private_changeset.id }, :body => new_changeset.to_s
1643       assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
1644
1645       ## Now try with the public user
1646       new_changeset = create_changeset_xml(:id => 1)
1647       new_tag = XML::Node.new "tag"
1648       new_tag["k"] = "tagtesting"
1649       new_tag["v"] = "valuetesting"
1650       new_changeset.find("//osm/changeset").first << new_tag
1651
1652       # try without any authorization
1653       @request.env["HTTP_AUTHORIZATION"] = nil
1654       put :update, :params => { :id => changeset.id }, :body => new_changeset.to_s
1655       assert_response :unauthorized
1656
1657       # try with the wrong authorization
1658       basic_authorization create(:user).email, "test"
1659       put :update, :params => { :id => changeset.id }, :body => new_changeset.to_s
1660       assert_response :conflict
1661
1662       # now this should work...
1663       basic_authorization user.email, "test"
1664       put :update, :params => { :id => changeset.id }, :body => new_changeset.to_s
1665       assert_response :success
1666
1667       assert_select "osm>changeset[id='#{changeset.id}']", 1
1668       assert_select "osm>changeset>tag", 1
1669       assert_select "osm>changeset>tag[k='tagtesting'][v='valuetesting']", 1
1670     end
1671
1672     ##
1673     # check that a user different from the one who opened the changeset
1674     # can't modify it.
1675     def test_changeset_update_invalid
1676       basic_authorization create(:user).email, "test"
1677
1678       changeset = create(:changeset)
1679       new_changeset = create_changeset_xml(:user => changeset.user, :id => changeset.id)
1680       new_tag = XML::Node.new "tag"
1681       new_tag["k"] = "testing"
1682       new_tag["v"] = "testing"
1683       new_changeset.find("//osm/changeset").first << new_tag
1684
1685       put :update, :params => { :id => changeset.id }, :body => new_changeset.to_s
1686       assert_response :conflict
1687     end
1688
1689     ##
1690     # check that a changeset can contain a certain max number of changes.
1691     ## FIXME should be changed to an integration test due to the with_controller
1692     def test_changeset_limits
1693       basic_authorization create(:user).email, "test"
1694
1695       # open a new changeset
1696       xml = "<osm><changeset/></osm>"
1697       put :create, :body => xml
1698       assert_response :success, "can't create a new changeset"
1699       cs_id = @response.body.to_i
1700
1701       # start the counter just short of where the changeset should finish.
1702       offset = 10
1703       # alter the database to set the counter on the changeset directly,
1704       # otherwise it takes about 6 minutes to fill all of them.
1705       changeset = Changeset.find(cs_id)
1706       changeset.num_changes = Changeset::MAX_ELEMENTS - offset
1707       changeset.save!
1708
1709       with_controller(NodesController.new) do
1710         # create a new node
1711         xml = "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
1712         put :create, :body => xml
1713         assert_response :success, "can't create a new node"
1714         node_id = @response.body.to_i
1715
1716         get :show, :params => { :id => node_id }
1717         assert_response :success, "can't read back new node"
1718         node_doc = XML::Parser.string(@response.body).parse
1719         node_xml = node_doc.find("//osm/node").first
1720
1721         # loop until we fill the changeset with nodes
1722         offset.times do |i|
1723           node_xml["lat"] = rand.to_s
1724           node_xml["lon"] = rand.to_s
1725           node_xml["version"] = (i + 1).to_s
1726
1727           put :update, :params => { :id => node_id }, :body => node_doc.to_s
1728           assert_response :success, "attempt #{i} should have succeeded"
1729         end
1730
1731         # trying again should fail
1732         node_xml["lat"] = rand.to_s
1733         node_xml["lon"] = rand.to_s
1734         node_xml["version"] = offset.to_s
1735
1736         put :update, :params => { :id => node_id }, :body => node_doc.to_s
1737         assert_response :conflict, "final attempt should have failed"
1738       end
1739
1740       changeset = Changeset.find(cs_id)
1741       assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
1742
1743       # check that the changeset is now closed as well
1744       assert_not(changeset.is_open?,
1745                  "changeset should have been auto-closed by exceeding " \
1746                  "element limit.")
1747     end
1748
1749     ##
1750     # check that the changeset download for a changeset with a redacted
1751     # element in it doesn't contain that element.
1752     def test_diff_download_redacted
1753       changeset = create(:changeset)
1754       node = create(:node, :with_history, :version => 2, :changeset => changeset)
1755       node_v1 = node.old_nodes.find_by(:version => 1)
1756       node_v1.redact!(create(:redaction))
1757
1758       get :download, :params => { :id => changeset.id }
1759       assert_response :success
1760
1761       assert_select "osmChange", 1
1762       # this changeset contains the node in versions 1 & 2, but 1 should
1763       # be hidden.
1764       assert_select "osmChange node[id='#{node.id}']", 1
1765       assert_select "osmChange node[id='#{node.id}'][version='1']", 0
1766     end
1767
1768     ##
1769     # test subscribe success
1770     def test_subscribe_success
1771       basic_authorization create(:user).email, "test"
1772       changeset = create(:changeset, :closed)
1773
1774       assert_difference "changeset.subscribers.count", 1 do
1775         post :subscribe, :params => { :id => changeset.id }
1776       end
1777       assert_response :success
1778
1779       # not closed changeset
1780       changeset = create(:changeset)
1781       assert_difference "changeset.subscribers.count", 1 do
1782         post :subscribe, :params => { :id => changeset.id }
1783       end
1784       assert_response :success
1785     end
1786
1787     ##
1788     # test subscribe fail
1789     def test_subscribe_fail
1790       user = create(:user)
1791
1792       # unauthorized
1793       changeset = create(:changeset, :closed)
1794       assert_no_difference "changeset.subscribers.count" do
1795         post :subscribe, :params => { :id => changeset.id }
1796       end
1797       assert_response :unauthorized
1798
1799       basic_authorization user.email, "test"
1800
1801       # bad changeset id
1802       assert_no_difference "changeset.subscribers.count" do
1803         post :subscribe, :params => { :id => 999111 }
1804       end
1805       assert_response :not_found
1806
1807       # trying to subscribe when already subscribed
1808       changeset = create(:changeset, :closed)
1809       changeset.subscribers.push(user)
1810       assert_no_difference "changeset.subscribers.count" do
1811         post :subscribe, :params => { :id => changeset.id }
1812       end
1813       assert_response :conflict
1814     end
1815
1816     ##
1817     # test unsubscribe success
1818     def test_unsubscribe_success
1819       user = create(:user)
1820       basic_authorization user.email, "test"
1821       changeset = create(:changeset, :closed)
1822       changeset.subscribers.push(user)
1823
1824       assert_difference "changeset.subscribers.count", -1 do
1825         post :unsubscribe, :params => { :id => changeset.id }
1826       end
1827       assert_response :success
1828
1829       # not closed changeset
1830       changeset = create(:changeset)
1831       changeset.subscribers.push(user)
1832
1833       assert_difference "changeset.subscribers.count", -1 do
1834         post :unsubscribe, :params => { :id => changeset.id }
1835       end
1836       assert_response :success
1837     end
1838
1839     ##
1840     # test unsubscribe fail
1841     def test_unsubscribe_fail
1842       # unauthorized
1843       changeset = create(:changeset, :closed)
1844       assert_no_difference "changeset.subscribers.count" do
1845         post :unsubscribe, :params => { :id => changeset.id }
1846       end
1847       assert_response :unauthorized
1848
1849       basic_authorization create(:user).email, "test"
1850
1851       # bad changeset id
1852       assert_no_difference "changeset.subscribers.count" do
1853         post :unsubscribe, :params => { :id => 999111 }
1854       end
1855       assert_response :not_found
1856
1857       # trying to unsubscribe when not subscribed
1858       changeset = create(:changeset, :closed)
1859       assert_no_difference "changeset.subscribers.count" do
1860         post :unsubscribe, :params => { :id => changeset.id }
1861       end
1862       assert_response :not_found
1863     end
1864
1865     private
1866
1867     ##
1868     # boilerplate for checking that certain changesets exist in the
1869     # output.
1870     def assert_changesets(changesets)
1871       assert_select "osm>changeset", changesets.size
1872       changesets.each do |changeset|
1873         assert_select "osm>changeset[id='#{changeset.id}']", 1
1874       end
1875     end
1876
1877     ##
1878     # update the changeset_id of a way element
1879     def update_changeset(xml, changeset_id)
1880       xml_attr_rewrite(xml, "changeset", changeset_id)
1881     end
1882
1883     ##
1884     # update an attribute in a way element
1885     def xml_attr_rewrite(xml, name, value)
1886       xml.find("//osm/way").first[name] = value.to_s
1887       xml
1888     end
1889
1890     ##
1891     # build XML for changesets
1892     def create_changeset_xml(user: nil, id: nil)
1893       root = XML::Document.new
1894       root.root = XML::Node.new "osm"
1895       cs = XML::Node.new "changeset"
1896       if user
1897         cs["user"] = user.display_name
1898         cs["uid"] = user.id.to_s
1899       end
1900       cs["id"] = id.to_s if id
1901       root.root << cs
1902       root
1903     end
1904   end
1905 end