+ diff = <<EOF
+<osmChange>
+ <create>
+ <node id='-1' lon='0' lat='0' changeset='#{changeset_id}'>
+ <tag k="foo" v="bar"/>
+ </node>
+ </create>
+ <modify>
+ <node id='-1' lon='1' lat='1' changeset='#{changeset_id}' version='1'/>
+ </modify>
+ <delete>
+ <node id='-1' lon='2' lat='2' changeset='#{changeset_id}' version='2'/>
+ </delete>
+</osmChange>
+EOF
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :success,
+ "can't upload a valid diff with re-used placeholders to changeset: #{@response.body}"
+
+ # check the response is well-formed
+ assert_select "diffResult>node", 3
+ assert_select "diffResult>node[old_id=-1]", 3
+ end
+
+ ##
+ # test what happens if a diff upload re-uses placeholder IDs in an
+ # illegal way.
+ def test_upload_placeholder_invalid
+ basic_authorization users(:public_user).email, "test"
+ changeset_id = changesets(:public_user_first_change).id
+
+ diff = <<EOF
+<osmChange>
+ <create>
+ <node id='-1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
+ <node id='-1' lon='1' lat='1' changeset='#{changeset_id}' version='1'/>
+ <node id='-1' lon='2' lat='2' changeset='#{changeset_id}' version='2'/>
+ </create>
+</osmChange>
+EOF
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :bad_request,
+ "shouldn't be able to re-use placeholder IDs"
+ end
+
+ ##
+ # test that uploading a way referencing invalid placeholders gives a
+ # proper error, not a 500.
+ def test_upload_placeholder_invalid_way
+ basic_authorization users(:public_user).email, "test"
+ changeset_id = changesets(:public_user_first_change).id
+
+ diff = <<EOF
+<osmChange>
+ <create>
+ <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
+ <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
+ <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
+ <way id="-1" changeset="#{changeset_id}" version="1">
+ <nd ref="-1"/>
+ <nd ref="-2"/>
+ <nd ref="-3"/>
+ <nd ref="-4"/>
+ </way>
+ </create>
+</osmChange>
+EOF
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :bad_request,
+ "shouldn't be able to use invalid placeholder IDs"
+ assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
+
+ # the same again, but this time use an existing way
+ diff = <<EOF
+<osmChange>
+ <create>
+ <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
+ <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
+ <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
+ <way id="1" changeset="#{changeset_id}" version="1">
+ <nd ref="-1"/>
+ <nd ref="-2"/>
+ <nd ref="-3"/>
+ <nd ref="-4"/>
+ </way>
+ </create>
+</osmChange>
+EOF
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :bad_request,
+ "shouldn't be able to use invalid placeholder IDs"
+ assert_equal "Placeholder node not found for reference -4 in way 1", @response.body
+ end
+
+ ##
+ # test that uploading a relation referencing invalid placeholders gives a
+ # proper error, not a 500.
+ def test_upload_placeholder_invalid_relation
+ basic_authorization users(:public_user).email, "test"
+ changeset_id = changesets(:public_user_first_change).id
+
+ diff = <<EOF
+<osmChange>
+ <create>
+ <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
+ <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
+ <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
+ <relation id="-1" changeset="#{changeset_id}" version="1">
+ <member type="node" role="foo" ref="-1"/>
+ <member type="node" role="foo" ref="-2"/>
+ <member type="node" role="foo" ref="-3"/>
+ <member type="node" role="foo" ref="-4"/>
+ </relation>
+ </create>
+</osmChange>
+EOF
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :bad_request,
+ "shouldn't be able to use invalid placeholder IDs"
+ assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
+
+ # the same again, but this time use an existing way
+ diff = <<EOF
+<osmChange>
+ <create>
+ <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
+ <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
+ <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
+ <relation id="1" changeset="#{changeset_id}" version="1">
+ <member type="node" role="foo" ref="-1"/>
+ <member type="node" role="foo" ref="-2"/>
+ <member type="node" role="foo" ref="-3"/>
+ <member type="way" role="bar" ref="-1"/>
+ </relation>
+ </create>
+</osmChange>
+EOF
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :bad_request,
+ "shouldn't be able to use invalid placeholder IDs"
+ assert_equal "Placeholder Way not found for reference -1 in relation 1.", @response.body
+ end
+
+ ##
+ # test what happens if a diff is uploaded containing only a node
+ # move.
+ def test_upload_node_move
+ basic_authorization users(:public_user).email, "test"
+
+ content "<osm><changeset>" +
+ "<tag k='created_by' v='osm test suite checking changesets'/>" +
+ "</changeset></osm>"
+ put :create
+ assert_response :success
+ changeset_id = @response.body.to_i
+
+ old_node = current_nodes(:visible_node)
+
+ diff = XML::Document.new
+ diff.root = XML::Node.new "osmChange"
+ modify = XML::Node.new "modify"
+ xml_old_node = old_node.to_xml_node
+ xml_old_node["lat"] = (2.0).to_s
+ xml_old_node["lon"] = (2.0).to_s
+ xml_old_node["changeset"] = changeset_id.to_s
+ modify << xml_old_node
+ diff.root << modify
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :success,
+ "diff should have uploaded OK"
+
+ # check the bbox
+ changeset = Changeset.find(changeset_id)
+ assert_equal 1*SCALE, changeset.min_lon, "min_lon should be 1 degree"
+ assert_equal 2*SCALE, changeset.max_lon, "max_lon should be 2 degrees"
+ assert_equal 1*SCALE, changeset.min_lat, "min_lat should be 1 degree"
+ assert_equal 2*SCALE, changeset.max_lat, "max_lat should be 2 degrees"
+ end
+
+ ##
+ # test what happens if a diff is uploaded adding a node to a way.
+ def test_upload_way_extend
+ basic_authorization users(:public_user).email, "test"
+
+ content "<osm><changeset>" +
+ "<tag k='created_by' v='osm test suite checking changesets'/>" +
+ "</changeset></osm>"
+ put :create
+ assert_response :success
+ changeset_id = @response.body.to_i
+
+ old_way = current_ways(:visible_way)
+
+ diff = XML::Document.new
+ diff.root = XML::Node.new "osmChange"
+ modify = XML::Node.new "modify"
+ xml_old_way = old_way.to_xml_node
+ nd_ref = XML::Node.new "nd"
+ nd_ref["ref"] = current_nodes(:visible_node).id.to_s
+ xml_old_way << nd_ref
+ xml_old_way["changeset"] = changeset_id.to_s
+ modify << xml_old_way
+ diff.root << modify
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :success,
+ "diff should have uploaded OK"
+
+ # check the bbox
+ changeset = Changeset.find(changeset_id)
+ assert_equal 1*SCALE, changeset.min_lon, "min_lon should be 1 degree"
+ assert_equal 3*SCALE, changeset.max_lon, "max_lon should be 3 degrees"
+ assert_equal 1*SCALE, changeset.min_lat, "min_lat should be 1 degree"
+ assert_equal 3*SCALE, changeset.max_lat, "max_lat should be 3 degrees"
+ end
+
+ ##
+ # test for more issues in #1568
+ def test_upload_empty_invalid
+ basic_authorization users(:public_user).email, "test"
+
+ [ "<osmChange/>",
+ "<osmChange></osmChange>",
+ "<osmChange><modify/></osmChange>",
+ "<osmChange><modify></modify></osmChange>"
+ ].each do |diff|
+ # upload it
+ content diff
+ post :upload, :id => changesets(:public_user_first_change).id
+ assert_response(:success, "should be able to upload " +
+ "empty changeset: " + diff)
+ end
+ end
+
+ ##
+ # test that the X-Error-Format header works to request XML errors
+ def test_upload_xml_errors
+ basic_authorization users(:public_user).email, "test"
+
+ # try and delete a node that is in use
+ diff = XML::Document.new
+ diff.root = XML::Node.new "osmChange"
+ delete = XML::Node.new "delete"
+ diff.root << delete
+ delete << current_nodes(:node_used_by_relationship).to_xml_node
+
+ # upload it
+ content diff
+ error_format "xml"
+ post :upload, :id => 2
+ assert_response :success,
+ "failed to return error in XML format"
+
+ # check the returned payload
+ assert_select "osmError[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
+ assert_select "osmError>status", 1
+ assert_select "osmError>message", 1
+
+ end
+
+ ##
+ # when we make some simple changes we get the same changes back from the
+ # diff download.
+ def test_diff_download_simple
+ ## First try with the normal user, which should get a forbidden
+ basic_authorization(users(:normal_user).email, "test")
+
+ # create a temporary changeset
+ content "<osm><changeset>" +
+ "<tag k='created_by' v='osm test suite checking changesets'/>" +
+ "</changeset></osm>"
+ put :create
+ assert_response :forbidden
+
+
+
+ ## Now try with the public user
+ basic_authorization(users(:public_user).email, "test")
+
+ # create a temporary changeset
+ content "<osm><changeset>" +
+ "<tag k='created_by' v='osm test suite checking changesets'/>" +
+ "</changeset></osm>"
+ put :create
+ assert_response :success
+ changeset_id = @response.body.to_i
+
+ # add a diff to it
+ diff = <<EOF
+<osmChange>
+ <modify>
+ <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
+ <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
+ <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
+ <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
+ <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
+ <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
+ <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
+ <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
+ </modify>
+</osmChange>
+EOF
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :success,
+ "can't upload multiple versions of an element in a diff: #{@response.body}"
+
+ get :download, :id => changeset_id
+ assert_response :success
+
+ assert_select "osmChange", 1
+ assert_select "osmChange>modify", 8
+ assert_select "osmChange>modify>node", 8
+ end
+
+ ##
+ # culled this from josm to ensure that nothing in the way that josm
+ # is formatting the request is causing it to fail.
+ #
+ # NOTE: the error turned out to be something else completely!
+ def test_josm_upload
+ basic_authorization(users(:public_user).email, "test")
+
+ # create a temporary changeset
+ content "<osm><changeset>" +
+ "<tag k='created_by' v='osm test suite checking changesets'/>" +
+ "</changeset></osm>"
+ put :create
+ assert_response :success
+ changeset_id = @response.body.to_i
+
+ diff = <<OSMFILE
+<osmChange version="0.6" generator="JOSM">
+<create version="0.6" generator="JOSM">
+ <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
+ <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
+ <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
+ <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
+ <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
+ <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
+ <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
+ <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
+ <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
+ <way id='-10' action='modiy' visible='true' changeset='#{changeset_id}'>
+ <nd ref='-1' />
+ <nd ref='-2' />
+ <nd ref='-3' />
+ <nd ref='-4' />
+ <nd ref='-5' />
+ <nd ref='-6' />
+ <nd ref='-7' />
+ <nd ref='-8' />
+ <nd ref='-9' />
+ <tag k='highway' v='residential' />
+ <tag k='name' v='Foobar Street' />
+ </way>
+</create>
+</osmChange>
+OSMFILE
+
+ # upload it
+ content diff
+ post :upload, :id => changeset_id
+ assert_response :success,
+ "can't upload a diff from JOSM: #{@response.body}"
+
+ get :download, :id => changeset_id
+ assert_response :success
+
+ assert_select "osmChange", 1
+ assert_select "osmChange>create>node", 9
+ assert_select "osmChange>create>way", 1
+ assert_select "osmChange>create>way>nd", 9
+ assert_select "osmChange>create>way>tag", 2
+ end
+
+ ##
+ # when we make some complex changes we get the same changes back from the
+ # diff download.
+ def test_diff_download_complex
+ basic_authorization(users(:public_user).email, "test")
+
+ # create a temporary changeset
+ content "<osm><changeset>" +
+ "<tag k='created_by' v='osm test suite checking changesets'/>" +
+ "</changeset></osm>"
+ put :create