5 class UploadsControllerTest < ActionDispatch::IntegrationTest
7 # test all routes which lead to this controller
10 { :path => "/api/0.6/changeset/1/upload", :method => :post },
11 { :controller => "api/changesets/uploads", :action => "create", :changeset_id => "1" }
15 def test_upload_when_unauthorized
16 changeset = create(:changeset)
17 node = create(:node, :latitude => 0, :longitude => 0)
22 <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
27 post api_changeset_upload_path(changeset), :params => diff
29 assert_response :unauthorized
32 assert_equal 0, changeset.num_changes
34 assert_equal 1, node.version
35 assert_equal 0, node.latitude
36 assert_equal 0, node.longitude
39 def test_upload_by_private_user
40 user = create(:user, :data_public => false)
41 changeset = create(:changeset, :user => user)
42 node = create(:node, :latitude => 0, :longitude => 0)
47 <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
52 auth_header = bearer_authorization_header user
54 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
56 assert_response :forbidden
59 assert_equal 0, changeset.num_changes
61 assert_equal 1, node.version
62 assert_equal 0, node.latitude
63 assert_equal 0, node.longitude
66 def test_upload_without_required_scope
68 changeset = create(:changeset, :user => user)
69 node = create(:node, :latitude => 0, :longitude => 0)
74 <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
79 auth_header = bearer_authorization_header user, :scopes => %w[read_prefs]
81 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
83 assert_response :forbidden
86 assert_equal 0, changeset.num_changes
88 assert_equal 1, node.version
89 assert_equal 0, node.latitude
90 assert_equal 0, node.longitude
93 def test_upload_with_required_scope
95 changeset = create(:changeset, :user => user)
96 node = create(:node, :latitude => 0, :longitude => 0)
101 <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
106 auth_header = bearer_authorization_header user, :scopes => %w[write_api]
108 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
110 assert_response :success
112 assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
113 assert_dom "> node", 1 do
114 assert_dom "> @old_id", node.id.to_s
115 assert_dom "> @new_id", node.id.to_s
116 assert_dom "> @new_version", "2"
121 assert_equal 1, changeset.num_changes
123 assert_equal 2, node.version
124 assert_equal 2 * GeoRecord::SCALE, node.latitude
125 assert_equal 1 * GeoRecord::SCALE, node.longitude
129 # try to upload with commands other than create, modify, or delete
130 def test_upload_unknown_action
131 changeset = create(:changeset)
136 <node id='1' lon='1' lat='1' changeset='#{changeset.id}' />
141 auth_header = bearer_authorization_header changeset.user
143 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
145 assert_response :bad_request
146 assert_equal "Unknown action ping, choices are create, modify, delete", @response.body
149 # -------------------------------------
150 # Test creating elements.
151 # -------------------------------------
153 def test_upload_create_elements
155 changeset = create(:changeset, :user => user)
157 way = create(:way_with_nodes, :nodes_count => 2)
158 relation = create(:relation)
163 <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
164 <tag k='foo' v='bar'/>
165 <tag k='baz' v='bat'/>
167 <way id='-1' changeset='#{changeset.id}'>
168 <nd ref='#{node.id}'/>
172 <relation id='-1' changeset='#{changeset.id}'>
173 <member type='way' role='some' ref='#{way.id}'/>
174 <member type='node' role='some' ref='#{node.id}'/>
175 <member type='relation' role='some' ref='#{relation.id}'/>
181 auth_header = bearer_authorization_header user
183 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
185 assert_response :success
187 new_node_id, new_way_id, new_rel_id = nil
188 assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
189 # inspect the response to find out what the new element IDs are
190 # check the old IDs are all present and negative one
191 # check the versions are present and equal one
192 assert_dom "> node", 1 do |(node_el)|
193 new_node_id = node_el["new_id"].to_i
194 assert_dom "> @old_id", "-1"
195 assert_dom "> @new_version", "1"
197 assert_dom "> way", 1 do |(way_el)|
198 new_way_id = way_el["new_id"].to_i
199 assert_dom "> @old_id", "-1"
200 assert_dom "> @new_version", "1"
202 assert_dom "> relation", 1 do |(rel_el)|
203 new_rel_id = rel_el["new_id"].to_i
204 assert_dom "> @old_id", "-1"
205 assert_dom "> @new_version", "1"
209 assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
210 assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
211 assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
215 # upload an element with a really long tag value
216 def test_upload_create_node_with_tag_too_long
217 changeset = create(:changeset)
222 <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
223 <tag k='foo' v='#{'x' * 256}'/>
229 auth_header = bearer_authorization_header changeset.user
231 assert_no_difference "Node.count" do
232 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
234 assert_response :bad_request
238 def test_upload_create_nodes_with_invalid_placeholder_reuse_in_one_action_block
239 changeset = create(:changeset)
244 <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
245 <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
250 auth_header = bearer_authorization_header changeset.user
252 assert_no_difference "Node.count" do
253 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
255 assert_response :bad_request
259 def test_upload_create_nodes_with_invalid_placeholder_reuse_in_two_action_blocks
260 changeset = create(:changeset)
265 <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
268 <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
273 auth_header = bearer_authorization_header changeset.user
275 assert_no_difference "Node.count" do
276 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
278 assert_response :bad_request
282 def test_upload_create_way_referring_node_placeholder_defined_later
283 changeset = create(:changeset)
288 <way id="-1" changeset="#{changeset.id}">
291 <node id="-1" lat="1" lon="2" changeset="#{changeset.id}"/>
296 auth_header = bearer_authorization_header changeset.user
298 assert_no_difference "Node.count" do
299 assert_no_difference "Way.count" do
300 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
302 assert_response :bad_request
305 assert_equal "Placeholder node not found for reference -1 in way -1", @response.body
308 def test_upload_create_way_referring_undefined_node_placeholder
309 changeset = create(:changeset)
314 <way id="-1" changeset="#{changeset.id}">
321 auth_header = bearer_authorization_header changeset.user
323 assert_no_difference "Way.count" do
324 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
326 assert_response :bad_request
328 assert_equal "Placeholder node not found for reference -1 in way -1", @response.body
331 def test_upload_create_existing_way_referring_undefined_node_placeholder
332 changeset = create(:changeset)
338 <way id="#{way.id}" changeset="#{changeset.id}" version="1">
345 auth_header = bearer_authorization_header changeset.user
347 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
349 assert_response :bad_request
350 assert_equal "Placeholder node not found for reference -1 in way #{way.id}", @response.body
353 assert_equal 1, way.version
356 def test_upload_create_relation_referring_undefined_node_placeholder
357 changeset = create(:changeset)
362 <relation id="-1" changeset="#{changeset.id}" version="1">
363 <member type="node" role="foo" ref="-1"/>
369 auth_header = bearer_authorization_header changeset.user
371 assert_no_difference "Relation.count" do
372 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
374 assert_response :bad_request
376 assert_equal "Placeholder Node not found for reference -1 in relation -1.", @response.body
379 def test_upload_create_existing_relation_referring_undefined_way_placeholder
380 changeset = create(:changeset)
381 relation = create(:relation)
386 <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
387 <member type="way" role="bar" ref="-1"/>
393 auth_header = bearer_authorization_header changeset.user
395 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
397 assert_response :bad_request
398 assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
401 assert_equal 1, relation.version
404 def test_upload_create_relations_with_circular_references
405 changeset = create(:changeset)
408 <osmChange version='0.6'>
410 <relation id='-2' version='0' changeset='#{changeset.id}'>
411 <member type='relation' role='' ref='-4' />
412 <tag k='type' v='route' />
413 <tag k='name' v='AtoB' />
415 <relation id='-3' version='0' changeset='#{changeset.id}'>
416 <tag k='type' v='route' />
417 <tag k='name' v='BtoA' />
419 <relation id='-4' version='0' changeset='#{changeset.id}'>
420 <member type='relation' role='' ref='-2' />
421 <member type='relation' role='' ref='-3' />
422 <tag k='type' v='route_master' />
423 <tag k='name' v='master' />
429 auth_header = bearer_authorization_header changeset.user
431 post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
433 assert_response :bad_request
434 assert_equal "Placeholder Relation not found for reference -4 in relation -2.", @response.body
437 # -------------------------------------
438 # Test modifying elements.
439 # -------------------------------------
441 def test_upload_modify_elements
443 changeset = create(:changeset, :user => user)
444 node = create(:node, :latitude => 0, :longitude => 0)
446 relation = create(:relation)
447 other_relation = create(:relation)
449 # create some tags, since we test that they are removed later
450 create(:node_tag, :node => node)
451 create(:way_tag, :way => way)
452 create(:relation_tag, :relation => relation)
454 # simple diff to change a node, way and relation by removing their tags
458 <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
459 <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
460 <nd ref='#{node.id}'/>
464 <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
465 <member type='way' role='some' ref='#{way.id}'/>
466 <member type='node' role='some' ref='#{node.id}'/>
467 <member type='relation' role='some' ref='#{other_relation.id}'/>
473 auth_header = bearer_authorization_header user
475 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
477 assert_response :success
479 assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
480 assert_dom "> node", 1 do
481 assert_dom "> @old_id", node.id.to_s
482 assert_dom "> @new_id", node.id.to_s
483 assert_dom "> @new_version", "2"
485 assert_dom "> way", 1 do
486 assert_dom "> @old_id", way.id.to_s
487 assert_dom "> @new_id", way.id.to_s
488 assert_dom "> @new_version", "2"
490 assert_dom "> relation", 1 do
491 assert_dom "> @old_id", relation.id.to_s
492 assert_dom "> @new_id", relation.id.to_s
493 assert_dom "> @new_version", "2"
498 assert_equal 3, changeset.num_changes
500 assert_equal 2, node.version
501 assert_equal 2 * GeoRecord::SCALE, node.latitude
502 assert_equal 1 * GeoRecord::SCALE, node.longitude
503 assert_equal 0, node.tags.size, "node #{node.id} should now have no tags"
505 assert_equal 2, way.version
506 assert_equal 0, way.tags.size, "way #{way.id} should now have no tags"
507 assert_equal [node], way.nodes
509 assert_equal 2, relation.version
510 assert_equal 0, relation.tags.size, "relation #{relation.id} should now have no tags"
511 assert_equal [["Way", way.id, "some"], ["Node", node.id, "some"], ["Relation", other_relation.id, "some"]], relation.members
515 # upload multiple versions of the same element in the same diff.
516 def test_upload_modify_multiple_node_versions
518 changeset = create(:changeset)
520 # change the location of a node multiple times, each time referencing
521 # the last version. doesn't this depend on version numbers being
526 <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset.id}' version='1'/>
527 <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset.id}' version='2'/>
528 <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset.id}' version='3'/>
529 <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset.id}' version='4'/>
530 <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset.id}' version='5'/>
531 <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset.id}' version='6'/>
532 <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset.id}' version='7'/>
533 <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset.id}' version='8'/>
538 auth_header = bearer_authorization_header changeset.user
540 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
542 assert_response :success
544 assert_dom "diffResult>node", 8
547 assert_equal 9, node.version
548 assert_equal 0.9 * GeoRecord::SCALE, node.latitude
549 assert_equal 0.9 * GeoRecord::SCALE, node.longitude
553 # upload multiple versions of the same element in the same diff, but
554 # keep the version numbers the same.
555 def test_upload_modify_duplicate_node_versions
556 node = create(:node, :latitude => 0, :longitude => 0)
557 changeset = create(:changeset)
562 <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
563 <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset.id}' version='1'/>
568 auth_header = bearer_authorization_header changeset.user
570 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
572 assert_response :conflict
575 assert_equal 1, node.version
576 assert_equal 0, node.latitude
577 assert_equal 0, node.longitude
581 # try to upload some elements without specifying the version
582 def test_upload_modify_missing_node_version
583 node = create(:node, :latitude => 0, :longitude => 0)
584 changeset = create(:changeset)
589 <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}'/>
594 auth_header = bearer_authorization_header changeset.user
596 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
598 assert_response :bad_request
601 assert_equal 1, node.version
602 assert_equal 0, node.latitude
603 assert_equal 0, node.longitude
607 # create a diff which references several changesets, which should cause
608 # a rollback and none of the diff gets committed
609 def test_upload_modify_with_references_to_different_changesets
610 changeset1 = create(:changeset)
611 changeset2 = create(:changeset, :user => changeset1.user)
612 node1 = create(:node)
613 node2 = create(:node)
615 # simple diff to create a node way and relation using placeholders
619 <node id='#{node1.id}' lon='0' lat='0' changeset='#{changeset1.id}' version='1'/>
622 <node id='#{node2.id}' lon='0' lat='0' changeset='#{changeset2.id}' version='1'/>
627 auth_header = bearer_authorization_header changeset1.user
629 post api_changeset_upload_path(changeset1), :params => diff, :headers => auth_header
631 assert_response :conflict
633 assert_nodes_are_equal(node1, Node.find(node1.id))
634 assert_nodes_are_equal(node2, Node.find(node2.id))
638 # upload a valid changeset which has a mixture of whitespace
639 # to check a bug https://github.com/openstreetmap/trac-tickets/issues/1565
640 def test_upload_modify_with_mixed_whitespace
641 changeset = create(:changeset)
643 way = create(:way_with_nodes, :nodes_count => 2)
644 relation = create(:relation)
645 other_relation = create(:relation)
646 create(:relation_tag, :relation => relation)
650 <modify><node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}'
652 <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='2'><tag k='k' v='v'/></node></modify>
654 <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'><member
655 type='way' role='some' ref='#{way.id}'/><member
656 type='node' role='some' ref='#{node.id}'/>
657 <member type='relation' role='some' ref='#{other_relation.id}'/>
659 </modify></osmChange>
662 auth_header = bearer_authorization_header changeset.user
664 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
666 assert_response :success
668 assert_dom "diffResult>node", 2
669 assert_dom "diffResult>relation", 1
671 assert_equal 1, Node.find(node.id).tags.size, "node #{node.id} should now have one tag"
672 assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
675 # -------------------------------------
676 # Test deleting elements.
677 # -------------------------------------
680 # test a complex delete where we delete elements which rely on each other
681 # in the same transaction.
682 def test_upload_delete_elements
683 changeset = create(:changeset)
684 super_relation = create(:relation)
685 used_relation = create(:relation)
686 used_way = create(:way)
687 used_node = create(:node)
688 create(:relation_member, :relation => super_relation, :member => used_relation)
689 create(:relation_member, :relation => super_relation, :member => used_way)
690 create(:relation_member, :relation => super_relation, :member => used_node)
692 diff = XML::Document.new
693 diff.root = XML::Node.new "osmChange"
694 delete = XML::Node.new "delete"
696 delete << xml_node_for_relation(super_relation)
697 delete << xml_node_for_relation(used_relation)
698 delete << xml_node_for_way(used_way)
699 delete << xml_node_for_node(used_node)
700 %w[node way relation].each do |type|
701 delete.find("//osmChange/delete/#{type}").each do |n|
702 n["changeset"] = changeset.id.to_s
706 auth_header = bearer_authorization_header changeset.user
708 post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
710 assert_response :success
712 assert_dom "diffResult", 1 do
713 assert_dom "> node", 1
714 assert_dom "> way", 1
715 assert_dom "> relation", 2
718 assert_not Node.find(used_node.id).visible
719 assert_not Way.find(used_way.id).visible
720 assert_not Relation.find(super_relation.id).visible
721 assert_not Relation.find(used_relation.id).visible
725 # test uploading a delete with no lat/lon, as they are optional in the osmChange spec.
726 def test_upload_delete_node_without_latlon
728 changeset = create(:changeset)
730 diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
732 auth_header = bearer_authorization_header changeset.user
734 post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
736 assert_response :success
738 assert_dom "diffResult", 1 do
739 assert_dom "> node", 1 do
740 assert_dom "> @old_id", node.id.to_s
741 assert_dom "> @new_id", 0
742 assert_dom "> @new_version", 0
747 assert_not node.visible
751 # test that deleting stuff in a transaction doesn't bypass the checks
752 # to ensure that used elements are not deleted.
753 def test_upload_delete_referenced_elements
754 changeset = create(:changeset)
755 relation = create(:relation)
756 other_relation = create(:relation)
757 used_way = create(:way)
758 used_node = create(:node)
759 create(:relation_member, :relation => relation, :member => used_way)
760 create(:relation_member, :relation => relation, :member => used_node)
762 diff = XML::Document.new
763 diff.root = XML::Node.new "osmChange"
764 delete = XML::Node.new "delete"
766 delete << xml_node_for_relation(other_relation)
767 delete << xml_node_for_way(used_way)
768 delete << xml_node_for_node(used_node)
769 %w[node way relation].each do |type|
770 delete.find("//osmChange/delete/#{type}").each do |n|
771 n["changeset"] = changeset.id.to_s
775 auth_header = bearer_authorization_header changeset.user
777 post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
779 assert_response :precondition_failed
780 assert_equal "Precondition failed: Way #{used_way.id} is still used by relations #{relation.id}.", @response.body
782 assert Node.find(used_node.id).visible
783 assert Way.find(used_way.id).visible
784 assert Relation.find(relation.id).visible
785 assert Relation.find(other_relation.id).visible
789 # test that a conditional delete of an in use object works.
790 def test_upload_delete_if_unused
791 changeset = create(:changeset)
792 super_relation = create(:relation)
793 used_relation = create(:relation)
794 used_way = create(:way)
795 used_node = create(:node)
796 create(:relation_member, :relation => super_relation, :member => used_relation)
797 create(:relation_member, :relation => super_relation, :member => used_way)
798 create(:relation_member, :relation => super_relation, :member => used_node)
800 diff = XML::Document.new
801 diff.root = XML::Node.new "osmChange"
802 delete = XML::Node.new "delete"
804 delete["if-unused"] = ""
805 delete << xml_node_for_relation(used_relation)
806 delete << xml_node_for_way(used_way)
807 delete << xml_node_for_node(used_node)
808 %w[node way relation].each do |type|
809 delete.find("//osmChange/delete/#{type}").each do |n|
810 n["changeset"] = changeset.id.to_s
814 auth_header = bearer_authorization_header changeset.user
816 post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
818 assert_response :success
820 assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
821 assert_dom "> node", 1 do
822 assert_dom "> @old_id", used_node.id.to_s
823 assert_dom "> @new_id", used_node.id.to_s
824 assert_dom "> @new_version", used_node.version.to_s
826 assert_dom "> way", 1 do
827 assert_dom "> @old_id", used_way.id.to_s
828 assert_dom "> @new_id", used_way.id.to_s
829 assert_dom "> @new_version", used_way.version.to_s
831 assert_dom "> relation", 1 do
832 assert_dom "> @old_id", used_relation.id.to_s
833 assert_dom "> @new_id", used_relation.id.to_s
834 assert_dom "> @new_version", used_relation.version.to_s
838 assert Node.find(used_node.id).visible
839 assert Way.find(used_way.id).visible
840 assert Relation.find(used_relation.id).visible
843 def test_upload_delete_with_multiple_blocks_and_if_unused
844 changeset = create(:changeset)
847 create(:way_node, :way => way, :node => node)
848 alone_node = create(:node)
851 <osmChange version='0.6'>
852 <delete version="0.6">
853 <node id="#{node.id}" version="#{node.version}" changeset="#{changeset.id}"/>
855 <delete version="0.6" if-unused="true">
856 <node id="#{alone_node.id}" version="#{alone_node.version}" changeset="#{changeset.id}"/>
861 auth_header = bearer_authorization_header changeset.user
863 post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
865 assert_response :precondition_failed
867 assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way.id}.", @response.body