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