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