Added tests for changeset close method.
[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   def basic_authorization(user, pass)
8     @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}")
9   end
10
11   def content(c)
12     @request.env["RAW_POST_DATA"] = c.to_s
13   end
14   
15   # -----------------------
16   # Test simple changeset creation
17   # -----------------------
18   
19   def test_create
20     basic_authorization "test@openstreetmap.org", "test"
21     
22     # Create the first user's changeset
23     content "<osm><changeset>" +
24       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
25       "</changeset></osm>"
26     put :create
27     
28     assert_response :success, "Creation of changeset did not return sucess status"
29     newid = @response.body
30   end
31   
32   def test_create_invalid
33     basic_authorization "test@openstreetmap.org", "test"
34     content "<osm><changeset></osm>"
35     put :create
36     assert_response :bad_request, "creating a invalid changeset should fail"
37   end
38
39   ##
40   # check that the changeset can be read and returns the correct
41   # document structure.
42   def test_read
43     changeset_id = changesets(:normal_user_first_change).id
44     get :read, :id => changeset_id
45     assert_response :success, "cannot get first changeset"
46     
47     assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
48     assert_select "osm>changeset[id=#{changeset_id}]", 1
49   end
50   
51   ##
52   # test that the user who opened a change can close it
53   def test_close
54     basic_authorization "test@openstreetmap.org", "test"
55
56     put :close, :id => changesets(:normal_user_first_change).id
57     assert_response :success
58   end
59
60   ##
61   # test that a different user can't close another user's changeset
62   def test_close_invalid
63     basic_authorization "test@example.com", "test"
64
65     put :close, :id => changesets(:normal_user_first_change).id
66     assert_response :conflict
67   end
68
69   ##
70   # upload something simple, but valid and check that it can 
71   # be read back ok.
72   def test_upload_simple_valid
73     basic_authorization "test@openstreetmap.org", "test"
74
75     # simple diff to change a node, way and relation by removing 
76     # their tags
77     diff = <<EOF
78 <osmChange>
79  <modify>
80   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
81   <way id='1' changeset='1' version='1'>
82    <nd ref='3'/>
83   </way>
84  </modify>
85  <modify>
86   <relation id='1' changeset='1' version='1'>
87    <member type='way' role='some' ref='3'/>
88    <member type='node' role='some' ref='5'/>
89    <member type='relation' role='some' ref='3'/>
90   </relation>
91  </modify>
92 </osmChange>
93 EOF
94
95     # upload it
96     content diff
97     post :upload, :id => 1
98     assert_response :success, 
99       "can't upload a simple valid diff to changeset: #{@response.body}"
100
101     # check that the changes made it into the database
102     assert_equal 0, Node.find(1).tags.size, "node 1 should now have no tags"
103     assert_equal 0, Way.find(1).tags.size, "way 1 should now have no tags"
104     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
105   end
106     
107   ##
108   # upload something which creates new objects using placeholders
109   def test_upload_create_valid
110     basic_authorization "test@openstreetmap.org", "test"
111
112     # simple diff to create a node way and relation using placeholders
113     diff = <<EOF
114 <osmChange>
115  <create>
116   <node id='-1' lon='0' lat='0' changeset='1'>
117    <tag k='foo' v='bar'/>
118    <tag k='baz' v='bat'/>
119   </node>
120   <way id='-1' changeset='1'>
121    <nd ref='3'/>
122   </way>
123  </create>
124  <create>
125   <relation id='-1' changeset='1'>
126    <member type='way' role='some' ref='3'/>
127    <member type='node' role='some' ref='5'/>
128    <member type='relation' role='some' ref='3'/>
129   </relation>
130  </create>
131 </osmChange>
132 EOF
133
134     # upload it
135     content diff
136     post :upload, :id => 1
137     assert_response :success, 
138       "can't upload a simple valid creation to changeset: #{@response.body}"
139
140     # check the returned payload
141     assert_select "diffResult[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
142     assert_select "diffResult>node", 1
143     assert_select "diffresult>way", 1
144     assert_select "diffResult>relation", 1
145
146     # inspect the response to find out what the new element IDs are
147     doc = XML::Parser.string(@response.body).parse
148     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
149     new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
150     new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
151
152     # check the old IDs are all present and negative one
153     assert_equal -1, doc.find("//diffResult/node").first["old_id"].to_i
154     assert_equal -1, doc.find("//diffResult/way").first["old_id"].to_i
155     assert_equal -1, doc.find("//diffResult/relation").first["old_id"].to_i
156
157     # check the versions are present and equal one
158     assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
159     assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
160     assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
161
162     # check that the changes made it into the database
163     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
164     assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
165     assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
166   end
167     
168   ##
169   # test a complex delete where we delete elements which rely on eachother
170   # in the same transaction.
171   def test_upload_delete
172     basic_authorization "test@openstreetmap.org", "test"
173
174     diff = XML::Document.new
175     diff.root = XML::Node.new "osmChange"
176     delete = XML::Node.new "delete"
177     diff.root << delete
178     delete << current_relations(:visible_relation).to_xml_node
179     delete << current_relations(:used_relation).to_xml_node
180     delete << current_ways(:used_way).to_xml_node
181     delete << current_nodes(:node_used_by_relationship).to_xml_node
182
183     # upload it
184     content diff
185     post :upload, :id => 1
186     assert_response :success, 
187       "can't upload a deletion diff to changeset: #{@response.body}"
188
189     # check that everything was deleted
190     assert_equal false, Node.find(current_nodes(:node_used_by_relationship).id).visible
191     assert_equal false, Way.find(current_ways(:used_way).id).visible
192     assert_equal false, Relation.find(current_relations(:visible_relation).id).visible
193     assert_equal false, Relation.find(current_relations(:used_relation).id).visible
194   end
195
196   ##
197   # test that deleting stuff in a transaction doesn't bypass the checks
198   # to ensure that used elements are not deleted.
199   def test_upload_delete_invalid
200     basic_authorization "test@openstreetmap.org", "test"
201
202     diff = XML::Document.new
203     diff.root = XML::Node.new "osmChange"
204     delete = XML::Node.new "delete"
205     diff.root << delete
206     delete << current_relations(:visible_relation).to_xml_node
207     delete << current_ways(:used_way).to_xml_node
208     delete << current_nodes(:node_used_by_relationship).to_xml_node
209
210     # upload it
211     content diff
212     post :upload, :id => 1
213     assert_response :precondition_failed, 
214       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
215
216     # check that nothing was, in fact, deleted
217     assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
218     assert_equal true, Way.find(current_ways(:used_way).id).visible
219     assert_equal true, Relation.find(current_relations(:visible_relation).id).visible
220   end
221
222   ##
223   # upload something which creates new objects and inserts them into
224   # existing containers using placeholders.
225   def test_upload_complex
226     basic_authorization "test@openstreetmap.org", "test"
227
228     # simple diff to create a node way and relation using placeholders
229     diff = <<EOF
230 <osmChange>
231  <create>
232   <node id='-1' lon='0' lat='0' changeset='1'>
233    <tag k='foo' v='bar'/>
234    <tag k='baz' v='bat'/>
235   </node>
236  </create>
237  <modify>
238   <way id='1' changeset='1' version='1'>
239    <nd ref='-1'/>
240    <nd ref='3'/>
241   </way>
242   <relation id='1' changeset='1' version='1'>
243    <member type='way' role='some' ref='3'/>
244    <member type='node' role='some' ref='-1'/>
245    <member type='relation' role='some' ref='3'/>
246   </relation>
247  </modify>
248 </osmChange>
249 EOF
250
251     # upload it
252     content diff
253     post :upload, :id => 1
254     assert_response :success, 
255       "can't upload a complex diff to changeset: #{@response.body}"
256
257     # check the returned payload
258     assert_select "diffResult[version=#{API_VERSION}][generator=\"#{GENERATOR}\"]", 1
259     assert_select "diffResult>node", 1
260     assert_select "diffResult>way", 1
261     assert_select "diffResult>relation", 1
262
263     # inspect the response to find out what the new element IDs are
264     doc = XML::Parser.string(@response.body).parse
265     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
266
267     # check that the changes made it into the database
268     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
269     assert_equal [new_node_id, 3], Way.find(1).nds, "way nodes should match"
270     Relation.find(1).members.each do |type,id,role|
271       if type == 'node'
272         assert_equal new_node_id, id, "relation should contain new node"
273       end
274     end
275   end
276     
277   ##
278   # create a diff which references several changesets, which should cause
279   # a rollback and none of the diff gets committed
280   def test_upload_invalid_changesets
281     basic_authorization "test@openstreetmap.org", "test"
282
283     # simple diff to create a node way and relation using placeholders
284     diff = <<EOF
285 <osmChange>
286  <modify>
287   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
288   <way id='1' changeset='1' version='1'>
289    <nd ref='3'/>
290   </way>
291  </modify>
292  <modify>
293   <relation id='1' changeset='1' version='1'>
294    <member type='way' role='some' ref='3'/>
295    <member type='node' role='some' ref='5'/>
296    <member type='relation' role='some' ref='3'/>
297   </relation>
298  </modify>
299  <create>
300   <node id='-1' changeset='4'>
301    <tag k='foo' v='bar'/>
302    <tag k='baz' v='bat'/>
303   </node>
304  </create>
305 </osmChange>
306 EOF
307     # cache the objects before uploading them
308     node = current_nodes(:visible_node)
309     way = current_ways(:visible_way)
310     rel = current_relations(:visible_relation)
311
312     # upload it
313     content diff
314     post :upload, :id => 1
315     assert_response :conflict, 
316       "uploading a diff with multiple changsets should have failed"
317
318     # check that objects are unmodified
319     assert_nodes_are_equal(node, Node.find(1))
320     assert_ways_are_equal(way, Way.find(1))
321   end
322     
323   ##
324   # upload multiple versions of the same element in the same diff.
325   def test_upload_multiple_valid
326     basic_authorization "test@openstreetmap.org", "test"
327
328     # change the location of a node multiple times, each time referencing
329     # the last version. doesn't this depend on version numbers being
330     # sequential?
331     diff = <<EOF
332 <osmChange>
333  <modify>
334   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
335   <node id='1' lon='1' lat='0' changeset='1' version='2'/>
336   <node id='1' lon='1' lat='1' changeset='1' version='3'/>
337   <node id='1' lon='1' lat='2' changeset='1' version='4'/>
338   <node id='1' lon='2' lat='2' changeset='1' version='5'/>
339   <node id='1' lon='3' lat='2' changeset='1' version='6'/>
340   <node id='1' lon='3' lat='3' changeset='1' version='7'/>
341   <node id='1' lon='9' lat='9' changeset='1' version='8'/>
342  </modify>
343 </osmChange>
344 EOF
345
346     # upload it
347     content diff
348     post :upload, :id => 1
349     assert_response :success, 
350       "can't upload multiple versions of an element in a diff: #{@response.body}"
351   end
352
353   ##
354   # upload multiple versions of the same element in the same diff, but
355   # keep the version numbers the same.
356   def test_upload_multiple_duplicate
357     basic_authorization "test@openstreetmap.org", "test"
358
359     diff = <<EOF
360 <osmChange>
361  <modify>
362   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
363   <node id='1' lon='1' lat='1' changeset='1' version='1'/>
364  </modify>
365 </osmChange>
366 EOF
367
368     # upload it
369     content diff
370     post :upload, :id => 1
371     assert_response :conflict, 
372       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
373   end
374
375   ##
376   # try to upload some elements without specifying the version
377   def test_upload_missing_version
378     basic_authorization "test@openstreetmap.org", "test"
379
380     diff = <<EOF
381 <osmChange>
382  <modify>
383   <node id='1' lon='1' lat='1' changeset='1'/>
384  </modify>
385 </osmChange>
386 EOF
387
388     # upload it
389     content diff
390     post :upload, :id => 1
391     assert_response :bad_request, 
392       "shouldn't be able to upload an element without version: #{@response.body}"
393   end
394   
395   ##
396   # try to upload with commands other than create, modify, or delete
397   def test_action_upload_invalid
398     basic_authorization "test@openstreetmap.org", "test"
399     
400     diff = <<EOF
401 <osmChange>
402   <ping>
403     <node id='1' lon='1' lat='1' changeset='1' />
404   </ping>
405 </osmChange>
406 EOF
407   content diff
408   post :upload, :id => 1
409   assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
410   assert_equal @response.body, "Unknown action ping, choices are create, modify, delete."
411   end
412
413   ##
414   # when we make some simple changes we get the same changes back from the 
415   # diff download.
416   def test_diff_download_simple
417     basic_authorization(users(:normal_user).email, "test")
418
419     # create a temporary changeset
420     content "<osm><changeset>" +
421       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
422       "</changeset></osm>"
423     put :create
424     assert_response :success
425     changeset_id = @response.body.to_i
426
427     # add a diff to it
428     diff = <<EOF
429 <osmChange>
430  <modify>
431   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
432   <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
433   <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
434   <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
435   <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
436   <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
437   <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
438   <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
439  </modify>
440 </osmChange>
441 EOF
442
443     # upload it
444     content diff
445     post :upload, :id => changeset_id
446     assert_response :success, 
447       "can't upload multiple versions of an element in a diff: #{@response.body}"
448     
449     get :download, :id => changeset_id
450     assert_response :success
451
452     assert_select "osmChange", 1
453     assert_select "osmChange>modify", 8
454     assert_select "osmChange>modify>node", 8
455   end
456   
457   ##
458   # when we make some complex changes we get the same changes back from the 
459   # diff download.
460   def test_diff_download_complex
461     basic_authorization(users(:normal_user).email, "test")
462
463     # create a temporary changeset
464     content "<osm><changeset>" +
465       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
466       "</changeset></osm>"
467     put :create
468     assert_response :success
469     changeset_id = @response.body.to_i
470
471     # add a diff to it
472     diff = <<EOF
473 <osmChange>
474  <delete>
475   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
476  </delete>
477  <create>
478   <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
479   <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
480   <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
481  </create>
482  <modify>
483   <node id='3' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
484   <way id='1' changeset='#{changeset_id}' version='1'>
485    <nd ref='3'/>
486    <nd ref='-1'/>
487    <nd ref='-2'/>
488    <nd ref='-3'/>
489   </way>
490  </modify>
491 </osmChange>
492 EOF
493
494     # upload it
495     content diff
496     post :upload, :id => changeset_id
497     assert_response :success, 
498       "can't upload multiple versions of an element in a diff: #{@response.body}"
499     
500     get :download, :id => changeset_id
501     assert_response :success
502
503     assert_select "osmChange", 1
504     assert_select "osmChange>create", 3
505     assert_select "osmChange>delete", 1
506     assert_select "osmChange>modify", 2
507     assert_select "osmChange>create>node", 3
508     assert_select "osmChange>delete>node", 1 
509     assert_select "osmChange>modify>node", 1
510     assert_select "osmChange>modify>way", 1
511   end
512
513   ##
514   # check that the bounding box of a changeset gets updated correctly
515   def test_changeset_bbox
516     basic_authorization "test@openstreetmap.org", "test"
517
518     # create a new changeset
519     content "<osm><changeset/></osm>"
520     put :create
521     assert_response :success, "Creating of changeset failed."
522     changeset_id = @response.body.to_i
523     
524     # add a single node to it
525     with_controller(NodeController.new) do
526       content "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
527       put :create
528       assert_response :success, "Couldn't create node."
529     end
530
531     # get the bounding box back from the changeset
532     get :read, :id => changeset_id
533     assert_response :success, "Couldn't read back changeset."
534     assert_select "osm>changeset[min_lon=1.0]", 1
535     assert_select "osm>changeset[max_lon=1.0]", 1
536     assert_select "osm>changeset[min_lat=2.0]", 1
537     assert_select "osm>changeset[max_lat=2.0]", 1
538
539     # add another node to it
540     with_controller(NodeController.new) do
541       content "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
542       put :create
543       assert_response :success, "Couldn't create second node."
544     end
545
546     # get the bounding box back from the changeset
547     get :read, :id => changeset_id
548     assert_response :success, "Couldn't read back changeset for the second time."
549     assert_select "osm>changeset[min_lon=1.0]", 1
550     assert_select "osm>changeset[max_lon=2.0]", 1
551     assert_select "osm>changeset[min_lat=1.0]", 1
552     assert_select "osm>changeset[max_lat=2.0]", 1
553
554     # add (delete) a way to it
555     with_controller(WayController.new) do
556       content update_changeset(current_ways(:visible_way).to_xml,
557                                changeset_id)
558       put :delete, :id => current_ways(:visible_way).id
559       assert_response :success, "Couldn't delete a way."
560     end
561
562     # get the bounding box back from the changeset
563     get :read, :id => changeset_id
564     assert_response :success, "Couldn't read back changeset for the third time."
565     assert_select "osm>changeset[min_lon=1.0]", 1
566     assert_select "osm>changeset[max_lon=3.1]", 1
567     assert_select "osm>changeset[min_lat=1.0]", 1
568     assert_select "osm>changeset[max_lat=3.1]", 1    
569   end
570
571   ##
572   # test that the changeset :include method works as it should
573   def test_changeset_include
574     basic_authorization "test@openstreetmap.org", "test"
575
576     # create a new changeset
577     content "<osm><changeset/></osm>"
578     put :create
579     assert_response :success, "Creating of changeset failed."
580     changeset_id = @response.body.to_i
581
582     # NOTE: the include method doesn't over-expand, like inserting
583     # a real method does. this is because we expect the client to 
584     # know what it is doing!
585     check_after_include(changeset_id,  1,  1, [ 1,  1,  1,  1])
586     check_after_include(changeset_id,  3,  3, [ 1,  1,  3,  3])
587     check_after_include(changeset_id,  4,  2, [ 1,  1,  4,  3])
588     check_after_include(changeset_id,  2,  2, [ 1,  1,  4,  3])
589     check_after_include(changeset_id, -1, -1, [-1, -1,  4,  3])
590     check_after_include(changeset_id, -2,  5, [-2, -1,  4,  5])
591   end
592
593   ##
594   # check searching for changesets by bbox
595   def test_changeset_by_bbox
596     get :query, :bbox => "-10,-10, 10, 10"
597     assert_response :success, "can't get changesets in bbox"
598     # FIXME: write the actual test bit after fixing the fixtures!
599   end
600
601   ##
602   # check updating tags on a changeset
603   def test_changeset_update
604     basic_authorization "test@openstreetmap.org", "test"
605
606     changeset = changesets(:normal_user_first_change)
607     new_changeset = changeset.to_xml
608     new_tag = XML::Node.new "tag"
609     new_tag['k'] = "testing"
610     new_tag['v'] = "testing"
611     new_changeset.find("//osm/changeset").first << new_tag
612
613     content new_changeset
614     put :update, :id => changeset.id
615     assert_response :success
616
617     assert_select "osm>changeset[id=#{changeset.id}]", 1
618     assert_select "osm>changeset>tag", 2
619     assert_select "osm>changeset>tag[k=testing][v=testing]", 1
620   end
621   
622   ##
623   # check that a user different from the one who opened the changeset
624   # can't modify it.
625   def test_changeset_update_invalid
626     basic_authorization "test@example.com", "test"
627
628     changeset = changesets(:normal_user_first_change)
629     new_changeset = changeset.to_xml
630     new_tag = XML::Node.new "tag"
631     new_tag['k'] = "testing"
632     new_tag['v'] = "testing"
633     new_changeset.find("//osm/changeset").first << new_tag
634
635     content new_changeset
636     put :update, :id => changeset.id
637     assert_response :conflict
638   end
639
640   #------------------------------------------------------------
641   # utility functions
642   #------------------------------------------------------------
643
644   ##
645   # call the include method and assert properties of the bbox
646   def check_after_include(changeset_id, lon, lat, bbox)
647     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
648     post :include, :id => changeset_id
649     assert_response :success, "Setting include of changeset failed: #{@response.body}"
650
651     # check exactly one changeset
652     assert_select "osm>changeset", 1
653     assert_select "osm>changeset[id=#{changeset_id}]", 1
654
655     # check the bbox
656     doc = XML::Parser.string(@response.body).parse
657     changeset = doc.find("//osm/changeset").first
658     assert_equal bbox[0], changeset['min_lon'].to_f, "min lon"
659     assert_equal bbox[1], changeset['min_lat'].to_f, "min lat"
660     assert_equal bbox[2], changeset['max_lon'].to_f, "max lon"
661     assert_equal bbox[3], changeset['max_lat'].to_f, "max lat"
662   end
663
664   ##
665   # update the changeset_id of a way element
666   def update_changeset(xml, changeset_id)
667     xml_attr_rewrite(xml, 'changeset', changeset_id)
668   end
669
670   ##
671   # update an attribute in a way element
672   def xml_attr_rewrite(xml, name, value)
673     xml.find("//osm/way").first[name] = value.to_s
674     return xml
675   end
676
677 end