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