1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'changeset_controller'
4 class ChangesetControllerTest < ActionController::TestCase
7 def basic_authorization(user, pass)
8 @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}")
12 @request.env["RAW_POST_DATA"] = c.to_s
15 # -----------------------
16 # Test simple changeset creation
17 # -----------------------
20 basic_authorization "test@openstreetmap.org", "test"
22 # Create the first user's changeset
23 content "<osm><changeset>" +
24 "<tag k='created_by' v='osm test suite checking changesets'/>" +
28 assert_response :success, "Creation of changeset did not return sucess status"
29 newid = @response.body
32 def test_create_invalid
33 basic_authorization "test@openstreetmap.org", "test"
34 content "<osm><changeset></osm>"
36 assert_response :bad_request, "creating a invalid changeset should fail"
40 # check that the changeset can be read and returns the correct
43 changeset_id = changesets(:normal_user_first_change).id
44 get :read, :id => changeset_id
45 assert_response :success, "cannot get first changeset"
47 assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
48 assert_select "osm>changeset[id=#{changeset_id}]", 1
52 # test that the user who opened a change can close it
54 basic_authorization "test@openstreetmap.org", "test"
56 put :close, :id => changesets(:normal_user_first_change).id
57 assert_response :success
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"
65 put :close, :id => changesets(:normal_user_first_change).id
66 assert_response :conflict
67 assert_equal "The user doesn't own that changeset", @response.body
71 # upload something simple, but valid and check that it can
73 def test_upload_simple_valid
74 basic_authorization "test@openstreetmap.org", "test"
76 # simple diff to change a node, way and relation by removing
81 <node id='1' lon='0' lat='0' changeset='1' version='1'/>
82 <way id='1' changeset='1' version='1'>
87 <relation id='1' changeset='1' version='1'>
88 <member type='way' role='some' ref='3'/>
89 <member type='node' role='some' ref='5'/>
90 <member type='relation' role='some' ref='3'/>
98 post :upload, :id => 1
99 assert_response :success,
100 "can't upload a simple valid diff to changeset: #{@response.body}"
102 # check that the changes made it into the database
103 assert_equal 0, Node.find(1).tags.size, "node 1 should now have no tags"
104 assert_equal 0, Way.find(1).tags.size, "way 1 should now have no tags"
105 assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
109 # upload something which creates new objects using placeholders
110 def test_upload_create_valid
111 basic_authorization "test@openstreetmap.org", "test"
113 # simple diff to create a node way and relation using placeholders
117 <node id='-1' lon='0' lat='0' changeset='1'>
118 <tag k='foo' v='bar'/>
119 <tag k='baz' v='bat'/>
121 <way id='-1' changeset='1'>
126 <relation id='-1' changeset='1'>
127 <member type='way' role='some' ref='3'/>
128 <member type='node' role='some' ref='5'/>
129 <member type='relation' role='some' ref='3'/>
137 post :upload, :id => 1
138 assert_response :success,
139 "can't upload a simple valid creation to changeset: #{@response.body}"
141 # check the returned payload
142 assert_select "diffResult[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
143 assert_select "diffResult>node", 1
144 assert_select "diffresult>way", 1
145 assert_select "diffResult>relation", 1
147 # inspect the response to find out what the new element IDs are
148 doc = XML::Parser.string(@response.body).parse
149 new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
150 new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
151 new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
153 # check the old IDs are all present and negative one
154 assert_equal -1, doc.find("//diffResult/node").first["old_id"].to_i
155 assert_equal -1, doc.find("//diffResult/way").first["old_id"].to_i
156 assert_equal -1, doc.find("//diffResult/relation").first["old_id"].to_i
158 # check the versions are present and equal one
159 assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
160 assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
161 assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
163 # check that the changes made it into the database
164 assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
165 assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
166 assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
170 # test a complex delete where we delete elements which rely on eachother
171 # in the same transaction.
172 def test_upload_delete
173 basic_authorization "test@openstreetmap.org", "test"
175 diff = XML::Document.new
176 diff.root = XML::Node.new "osmChange"
177 delete = XML::Node.new "delete"
179 delete << current_relations(:visible_relation).to_xml_node
180 delete << current_relations(:used_relation).to_xml_node
181 delete << current_ways(:used_way).to_xml_node
182 delete << current_nodes(:node_used_by_relationship).to_xml_node
186 post :upload, :id => 1
187 assert_response :success,
188 "can't upload a deletion diff to changeset: #{@response.body}"
190 # check that everything was deleted
191 assert_equal false, Node.find(current_nodes(:node_used_by_relationship).id).visible
192 assert_equal false, Way.find(current_ways(:used_way).id).visible
193 assert_equal false, Relation.find(current_relations(:visible_relation).id).visible
194 assert_equal false, Relation.find(current_relations(:used_relation).id).visible
198 # test that deleting stuff in a transaction doesn't bypass the checks
199 # to ensure that used elements are not deleted.
200 def test_upload_delete_invalid
201 basic_authorization "test@openstreetmap.org", "test"
203 diff = XML::Document.new
204 diff.root = XML::Node.new "osmChange"
205 delete = XML::Node.new "delete"
207 delete << current_relations(:visible_relation).to_xml_node
208 delete << current_ways(:used_way).to_xml_node
209 delete << current_nodes(:node_used_by_relationship).to_xml_node
213 post :upload, :id => 1
214 assert_response :precondition_failed,
215 "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
217 # check that nothing was, in fact, deleted
218 assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
219 assert_equal true, Way.find(current_ways(:used_way).id).visible
220 assert_equal true, Relation.find(current_relations(:visible_relation).id).visible
224 # upload something which creates new objects and inserts them into
225 # existing containers using placeholders.
226 def test_upload_complex
227 basic_authorization "test@openstreetmap.org", "test"
229 # simple diff to create a node way and relation using placeholders
233 <node id='-1' lon='0' lat='0' changeset='1'>
234 <tag k='foo' v='bar'/>
235 <tag k='baz' v='bat'/>
239 <way id='1' changeset='1' version='1'>
243 <relation id='1' changeset='1' version='1'>
244 <member type='way' role='some' ref='3'/>
245 <member type='node' role='some' ref='-1'/>
246 <member type='relation' role='some' ref='3'/>
254 post :upload, :id => 1
255 assert_response :success,
256 "can't upload a complex diff to changeset: #{@response.body}"
258 # check the returned payload
259 assert_select "diffResult[version=#{API_VERSION}][generator=\"#{GENERATOR}\"]", 1
260 assert_select "diffResult>node", 1
261 assert_select "diffResult>way", 1
262 assert_select "diffResult>relation", 1
264 # inspect the response to find out what the new element IDs are
265 doc = XML::Parser.string(@response.body).parse
266 new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
268 # check that the changes made it into the database
269 assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
270 assert_equal [new_node_id, 3], Way.find(1).nds, "way nodes should match"
271 Relation.find(1).members.each do |type,id,role|
273 assert_equal new_node_id, id, "relation should contain new node"
279 # create a diff which references several changesets, which should cause
280 # a rollback and none of the diff gets committed
281 def test_upload_invalid_changesets
282 basic_authorization "test@openstreetmap.org", "test"
284 # simple diff to create a node way and relation using placeholders
288 <node id='1' lon='0' lat='0' changeset='1' version='1'/>
289 <way id='1' changeset='1' version='1'>
294 <relation id='1' changeset='1' version='1'>
295 <member type='way' role='some' ref='3'/>
296 <member type='node' role='some' ref='5'/>
297 <member type='relation' role='some' ref='3'/>
301 <node id='-1' changeset='4'>
302 <tag k='foo' v='bar'/>
303 <tag k='baz' v='bat'/>
308 # cache the objects before uploading them
309 node = current_nodes(:visible_node)
310 way = current_ways(:visible_way)
311 rel = current_relations(:visible_relation)
315 post :upload, :id => 1
316 assert_response :conflict,
317 "uploading a diff with multiple changsets should have failed"
319 # check that objects are unmodified
320 assert_nodes_are_equal(node, Node.find(1))
321 assert_ways_are_equal(way, Way.find(1))
325 # upload multiple versions of the same element in the same diff.
326 def test_upload_multiple_valid
327 basic_authorization "test@openstreetmap.org", "test"
329 # change the location of a node multiple times, each time referencing
330 # the last version. doesn't this depend on version numbers being
335 <node id='1' lon='0' lat='0' changeset='1' version='1'/>
336 <node id='1' lon='1' lat='0' changeset='1' version='2'/>
337 <node id='1' lon='1' lat='1' changeset='1' version='3'/>
338 <node id='1' lon='1' lat='2' changeset='1' version='4'/>
339 <node id='1' lon='2' lat='2' changeset='1' version='5'/>
340 <node id='1' lon='3' lat='2' changeset='1' version='6'/>
341 <node id='1' lon='3' lat='3' changeset='1' version='7'/>
342 <node id='1' lon='9' lat='9' changeset='1' version='8'/>
349 post :upload, :id => 1
350 assert_response :success,
351 "can't upload multiple versions of an element in a diff: #{@response.body}"
355 # upload multiple versions of the same element in the same diff, but
356 # keep the version numbers the same.
357 def test_upload_multiple_duplicate
358 basic_authorization "test@openstreetmap.org", "test"
363 <node id='1' lon='0' lat='0' changeset='1' version='1'/>
364 <node id='1' lon='1' lat='1' changeset='1' version='1'/>
371 post :upload, :id => 1
372 assert_response :conflict,
373 "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
377 # try to upload some elements without specifying the version
378 def test_upload_missing_version
379 basic_authorization "test@openstreetmap.org", "test"
384 <node id='1' lon='1' lat='1' changeset='1'/>
391 post :upload, :id => 1
392 assert_response :bad_request,
393 "shouldn't be able to upload an element without version: #{@response.body}"
397 # try to upload with commands other than create, modify, or delete
398 def test_action_upload_invalid
399 basic_authorization "test@openstreetmap.org", "test"
404 <node id='1' lon='1' lat='1' changeset='1' />
409 post :upload, :id => 1
410 assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
411 assert_equal @response.body, "Unknown action ping, choices are create, modify, delete."
415 # when we make some simple changes we get the same changes back from the
417 def test_diff_download_simple
418 basic_authorization(users(:normal_user).email, "test")
420 # create a temporary changeset
421 content "<osm><changeset>" +
422 "<tag k='created_by' v='osm test suite checking changesets'/>" +
425 assert_response :success
426 changeset_id = @response.body.to_i
432 <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
433 <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
434 <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
435 <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
436 <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
437 <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
438 <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
439 <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
446 post :upload, :id => changeset_id
447 assert_response :success,
448 "can't upload multiple versions of an element in a diff: #{@response.body}"
450 get :download, :id => changeset_id
451 assert_response :success
453 assert_select "osmChange", 1
454 assert_select "osmChange>modify", 8
455 assert_select "osmChange>modify>node", 8
459 # when we make some complex changes we get the same changes back from the
461 def test_diff_download_complex
462 basic_authorization(users(:normal_user).email, "test")
464 # create a temporary changeset
465 content "<osm><changeset>" +
466 "<tag k='created_by' v='osm test suite checking changesets'/>" +
469 assert_response :success
470 changeset_id = @response.body.to_i
476 <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
479 <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
480 <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
481 <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
484 <node id='3' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
485 <way id='1' changeset='#{changeset_id}' version='1'>
497 post :upload, :id => changeset_id
498 assert_response :success,
499 "can't upload multiple versions of an element in a diff: #{@response.body}"
501 get :download, :id => changeset_id
502 assert_response :success
504 assert_select "osmChange", 1
505 assert_select "osmChange>create", 3
506 assert_select "osmChange>delete", 1
507 assert_select "osmChange>modify", 2
508 assert_select "osmChange>create>node", 3
509 assert_select "osmChange>delete>node", 1
510 assert_select "osmChange>modify>node", 1
511 assert_select "osmChange>modify>way", 1
515 # check that the bounding box of a changeset gets updated correctly
516 def test_changeset_bbox
517 basic_authorization "test@openstreetmap.org", "test"
519 # create a new changeset
520 content "<osm><changeset/></osm>"
522 assert_response :success, "Creating of changeset failed."
523 changeset_id = @response.body.to_i
525 # add a single node to it
526 with_controller(NodeController.new) do
527 content "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
529 assert_response :success, "Couldn't create node."
532 # get the bounding box back from the changeset
533 get :read, :id => changeset_id
534 assert_response :success, "Couldn't read back changeset."
535 assert_select "osm>changeset[min_lon=1.0]", 1
536 assert_select "osm>changeset[max_lon=1.0]", 1
537 assert_select "osm>changeset[min_lat=2.0]", 1
538 assert_select "osm>changeset[max_lat=2.0]", 1
540 # add another node to it
541 with_controller(NodeController.new) do
542 content "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
544 assert_response :success, "Couldn't create second node."
547 # get the bounding box back from the changeset
548 get :read, :id => changeset_id
549 assert_response :success, "Couldn't read back changeset for the second time."
550 assert_select "osm>changeset[min_lon=1.0]", 1
551 assert_select "osm>changeset[max_lon=2.0]", 1
552 assert_select "osm>changeset[min_lat=1.0]", 1
553 assert_select "osm>changeset[max_lat=2.0]", 1
555 # add (delete) a way to it
556 with_controller(WayController.new) do
557 content update_changeset(current_ways(:visible_way).to_xml,
559 put :delete, :id => current_ways(:visible_way).id
560 assert_response :success, "Couldn't delete a way."
563 # get the bounding box back from the changeset
564 get :read, :id => changeset_id
565 assert_response :success, "Couldn't read back changeset for the third time."
566 assert_select "osm>changeset[min_lon=1.0]", 1
567 assert_select "osm>changeset[max_lon=3.1]", 1
568 assert_select "osm>changeset[min_lat=1.0]", 1
569 assert_select "osm>changeset[max_lat=3.1]", 1
573 # test that the changeset :include method works as it should
574 def test_changeset_include
575 basic_authorization "test@openstreetmap.org", "test"
577 # create a new changeset
578 content "<osm><changeset/></osm>"
580 assert_response :success, "Creating of changeset failed."
581 changeset_id = @response.body.to_i
583 # NOTE: the include method doesn't over-expand, like inserting
584 # a real method does. this is because we expect the client to
585 # know what it is doing!
586 check_after_include(changeset_id, 1, 1, [ 1, 1, 1, 1])
587 check_after_include(changeset_id, 3, 3, [ 1, 1, 3, 3])
588 check_after_include(changeset_id, 4, 2, [ 1, 1, 4, 3])
589 check_after_include(changeset_id, 2, 2, [ 1, 1, 4, 3])
590 check_after_include(changeset_id, -1, -1, [-1, -1, 4, 3])
591 check_after_include(changeset_id, -2, 5, [-2, -1, 4, 5])
595 # check searching for changesets by bbox
596 def test_changeset_by_bbox
597 get :query, :bbox => "-10,-10, 10, 10"
598 assert_response :success, "can't get changesets in bbox"
599 assert_changesets [1,4]
601 get :query, :bbox => "4.5,4.5,4.6,4.6"
602 assert_response :success, "can't get changesets in bbox"
603 assert_changesets [1]
605 # can't get changesets of user 1 without authenticating
606 get :query, :user => users(:normal_user).id
607 assert_response :not_found, "shouldn't be able to get changesets by non-public user"
609 # but this should work
610 basic_authorization "test@openstreetmap.org", "test"
611 get :query, :user => users(:normal_user).id
612 assert_response :success, "can't get changesets by user"
613 assert_changesets [1,3,4]
615 get :query, :user => users(:normal_user).id, :open => true
616 assert_response :success, "can't get changesets by user and open"
617 assert_changesets [1,4]
619 get :query, :time => '2007-12-31'
620 assert_response :success, "can't get changesets by time-since"
621 assert_changesets [2,4,5]
623 get :query, :time => '2008-01-01T12:34Z'
624 assert_response :success, "can't get changesets by time-since with hour"
625 assert_changesets [2]
627 get :query, :time => '2007-12-31T23:59Z,2008-01-01T00:01Z'
628 assert_response :success, "can't get changesets by time-range"
629 assert_changesets [4,5]
633 # check updating tags on a changeset
634 def test_changeset_update
635 basic_authorization "test@openstreetmap.org", "test"
637 changeset = changesets(:normal_user_first_change)
638 new_changeset = changeset.to_xml
639 new_tag = XML::Node.new "tag"
640 new_tag['k'] = "tagtesting"
641 new_tag['v'] = "valuetesting"
642 new_changeset.find("//osm/changeset").first << new_tag
644 content new_changeset
645 put :update, :id => changeset.id
646 assert_response :success
648 assert_select "osm>changeset[id=#{changeset.id}]", 1
649 assert_select "osm>changeset>tag", 2
650 assert_select "osm>changeset>tag[k=tagtesting][v=valuetesting]", 1
654 # check that a user different from the one who opened the changeset
656 def test_changeset_update_invalid
657 basic_authorization "test@example.com", "test"
659 changeset = changesets(:normal_user_first_change)
660 new_changeset = changeset.to_xml
661 new_tag = XML::Node.new "tag"
662 new_tag['k'] = "testing"
663 new_tag['v'] = "testing"
664 new_changeset.find("//osm/changeset").first << new_tag
666 content new_changeset
667 put :update, :id => changeset.id
668 assert_response :conflict
671 #------------------------------------------------------------
673 #------------------------------------------------------------
676 # boilerplate for checking that certain changesets exist in the
678 def assert_changesets(ids)
679 assert_select "osm>changeset", ids.size
681 assert_select "osm>changeset[id=#{id}]", 1
686 # call the include method and assert properties of the bbox
687 def check_after_include(changeset_id, lon, lat, bbox)
688 content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
689 post :include, :id => changeset_id
690 assert_response :success, "Setting include of changeset failed: #{@response.body}"
692 # check exactly one changeset
693 assert_select "osm>changeset", 1
694 assert_select "osm>changeset[id=#{changeset_id}]", 1
697 doc = XML::Parser.string(@response.body).parse
698 changeset = doc.find("//osm/changeset").first
699 assert_equal bbox[0], changeset['min_lon'].to_f, "min lon"
700 assert_equal bbox[1], changeset['min_lat'].to_f, "min lat"
701 assert_equal bbox[2], changeset['max_lon'].to_f, "max lon"
702 assert_equal bbox[3], changeset['max_lat'].to_f, "max lat"
706 # update the changeset_id of a way element
707 def update_changeset(xml, changeset_id)
708 xml_attr_rewrite(xml, 'changeset', changeset_id)
712 # update an attribute in a way element
713 def xml_attr_rewrite(xml, name, value)
714 xml.find("//osm/way").first[name] = value.to_s