source: subversion/sites/rails_port/test/functional/relation_controller_test.rb @ 14651

Last change on this file since 14651 was 14651, checked in by zere, 10 years ago

Adding more tests for updating relation tags.

File size: 24.5 KB
Line 
1require File.dirname(__FILE__) + '/../test_helper'
2require 'relation_controller'
3
4class RelationControllerTest < ActionController::TestCase
5  api_fixtures
6
7  # -------------------------------------
8  # Test reading relations.
9  # -------------------------------------
10
11  def test_read
12    # check that a visible relation is returned properly
13    get :read, :id => current_relations(:visible_relation).id
14    assert_response :success
15
16    # check that an invisible relation is not returned
17    get :read, :id => current_relations(:invisible_relation).id
18    assert_response :gone
19
20    # check chat a non-existent relation is not returned
21    get :read, :id => 0
22    assert_response :not_found
23  end
24
25  ##
26  # check that all relations containing a particular node, and no extra
27  # relations, are returned from the relations_for_node call.
28  def test_relations_for_node
29    check_relations_for_element(:relations_for_node, "node", 
30                                current_nodes(:node_used_by_relationship).id,
31                                [ :visible_relation, :used_relation ])
32  end
33
34  def test_relations_for_way
35    check_relations_for_element(:relations_for_way, "way",
36                                current_ways(:used_way).id,
37                                [ :visible_relation ])
38  end
39
40  def test_relations_for_relation
41    check_relations_for_element(:relations_for_relation, "relation",
42                                current_relations(:used_relation).id,
43                                [ :visible_relation ])
44  end
45
46  def check_relations_for_element(method, type, id, expected_relations)
47    # check the "relations for relation" mode
48    get method, :id => id
49    assert_response :success
50
51    # count one osm element
52    assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
53
54    # we should have only the expected number of relations
55    assert_select "osm>relation", expected_relations.size
56
57    # and each of them should contain the node we originally searched for
58    expected_relations.each do |r|
59      relation_id = current_relations(r).id
60      assert_select "osm>relation#?", relation_id
61      assert_select "osm>relation#?>member[type=\"#{type}\"][ref=#{id}]", relation_id
62    end
63  end
64
65  def test_full
66    # check the "full" mode
67    get :full, :id => current_relations(:visible_relation).id
68    assert_response :success
69    # FIXME check whether this contains the stuff we want!
70    if $VERBOSE
71        print @response.body
72    end
73  end
74
75  # -------------------------------------
76  # Test simple relation creation.
77  # -------------------------------------
78
79  def test_create
80    basic_authorization "test@openstreetmap.org", "test"
81   
82    # put the relation in a dummy fixture changset
83    changeset_id = changesets(:normal_user_first_change).id
84
85    # create an relation without members
86    content "<osm><relation changeset='#{changeset_id}'><tag k='test' v='yes' /></relation></osm>"
87    put :create
88    # hope for success
89    assert_response :success, 
90        "relation upload did not return success status"
91    # read id of created relation and search for it
92    relationid = @response.body
93    checkrelation = Relation.find(relationid)
94    assert_not_nil checkrelation, 
95        "uploaded relation not found in data base after upload"
96    # compare values
97    assert_equal checkrelation.members.length, 0, 
98        "saved relation contains members but should not"
99    assert_equal checkrelation.tags.length, 1, 
100        "saved relation does not contain exactly one tag"
101    assert_equal changeset_id, checkrelation.changeset.id,
102        "saved relation does not belong in the changeset it was assigned to"
103    assert_equal users(:normal_user).id, checkrelation.changeset.user_id, 
104        "saved relation does not belong to user that created it"
105    assert_equal true, checkrelation.visible, 
106        "saved relation is not visible"
107    # ok the relation is there but can we also retrieve it?
108    get :read, :id => relationid
109    assert_response :success
110
111
112    ###
113    # create an relation with a node as member
114    # This time try with a role attribute in the relation
115    nid = current_nodes(:used_node_1).id
116    content "<osm><relation changeset='#{changeset_id}'>" +
117      "<member  ref='#{nid}' type='node' role='some'/>" +
118      "<tag k='test' v='yes' /></relation></osm>"
119    put :create
120    # hope for success
121    assert_response :success, 
122        "relation upload did not return success status"
123    # read id of created relation and search for it
124    relationid = @response.body
125    checkrelation = Relation.find(relationid)
126    assert_not_nil checkrelation, 
127        "uploaded relation not found in data base after upload"
128    # compare values
129    assert_equal checkrelation.members.length, 1, 
130        "saved relation does not contain exactly one member"
131    assert_equal checkrelation.tags.length, 1, 
132        "saved relation does not contain exactly one tag"
133    assert_equal changeset_id, checkrelation.changeset.id,
134        "saved relation does not belong in the changeset it was assigned to"
135    assert_equal users(:normal_user).id, checkrelation.changeset.user_id, 
136        "saved relation does not belong to user that created it"
137    assert_equal true, checkrelation.visible, 
138        "saved relation is not visible"
139    # ok the relation is there but can we also retrieve it?
140   
141    get :read, :id => relationid
142    assert_response :success
143   
144   
145    ###
146    # create an relation with a node as member, this time test that we don't
147    # need a role attribute to be included
148    nid = current_nodes(:used_node_1).id
149    content "<osm><relation changeset='#{changeset_id}'>" +
150      "<member  ref='#{nid}' type='node'/>"+
151      "<tag k='test' v='yes' /></relation></osm>"
152    put :create
153    # hope for success
154    assert_response :success, 
155        "relation upload did not return success status"
156    # read id of created relation and search for it
157    relationid = @response.body
158    checkrelation = Relation.find(relationid)
159    assert_not_nil checkrelation, 
160        "uploaded relation not found in data base after upload"
161    # compare values
162    assert_equal checkrelation.members.length, 1, 
163        "saved relation does not contain exactly one member"
164    assert_equal checkrelation.tags.length, 1, 
165        "saved relation does not contain exactly one tag"
166    assert_equal changeset_id, checkrelation.changeset.id,
167        "saved relation does not belong in the changeset it was assigned to"
168    assert_equal users(:normal_user).id, checkrelation.changeset.user_id, 
169        "saved relation does not belong to user that created it"
170    assert_equal true, checkrelation.visible, 
171        "saved relation is not visible"
172    # ok the relation is there but can we also retrieve it?
173   
174    get :read, :id => relationid
175    assert_response :success
176
177    ###
178    # create an relation with a way and a node as members
179    nid = current_nodes(:used_node_1).id
180    wid = current_ways(:used_way).id
181    content "<osm><relation changeset='#{changeset_id}'>" +
182      "<member type='node' ref='#{nid}' role='some'/>" +
183      "<member type='way' ref='#{wid}' role='other'/>" +
184      "<tag k='test' v='yes' /></relation></osm>"
185    put :create
186    # hope for success
187    assert_response :success, 
188        "relation upload did not return success status"
189    # read id of created relation and search for it
190    relationid = @response.body
191    checkrelation = Relation.find(relationid)
192    assert_not_nil checkrelation, 
193        "uploaded relation not found in data base after upload"
194    # compare values
195    assert_equal checkrelation.members.length, 2, 
196        "saved relation does not have exactly two members"
197    assert_equal checkrelation.tags.length, 1, 
198        "saved relation does not contain exactly one tag"
199    assert_equal changeset_id, checkrelation.changeset.id,
200        "saved relation does not belong in the changeset it was assigned to"
201    assert_equal users(:normal_user).id, checkrelation.changeset.user_id, 
202        "saved relation does not belong to user that created it"
203    assert_equal true, checkrelation.visible, 
204        "saved relation is not visible"
205    # ok the relation is there but can we also retrieve it?
206    get :read, :id => relationid
207    assert_response :success
208
209  end
210
211  # ------------------------------------
212  # Test updating relations
213  # ------------------------------------
214
215  ##
216  # test that, when tags are updated on a relation, the correct things
217  # happen to the correct tables and the API gives sensible results.
218  # this is to test a case that gregory marler noticed and posted to
219  # josm-dev.
220  def test_update_relation_tags
221    basic_authorization "test@example.com", "test"
222    rel_id = current_relations(:multi_tag_relation).id
223    cs_id = changesets(:public_user_first_change).id
224
225    with_relation(rel_id) do |rel|
226      # alter one of the tags
227      tag = rel.find("//osm/relation/tag").first
228      tag['v'] = 'some changed value'
229      update_changeset(rel, cs_id)
230
231      # check that the downloaded tags are the same as the uploaded tags...
232      new_version = with_update(rel) do |new_rel|
233        assert_tags_equal rel, new_rel
234      end
235
236      # check the original one in the current_* table again
237      with_relation(rel_id) { |r| assert_tags_equal rel, r }
238
239      # now check the version in the history
240      with_relation(rel_id, new_version) { |r| assert_tags_equal rel, r }
241    end
242  end
243
244  ##
245  # test that, when tags are updated on a relation when using the diff
246  # upload function, the correct things happen to the correct tables
247  # and the API gives sensible results. this is to test a case that
248  # gregory marler noticed and posted to josm-dev.
249  def test_update_relation_tags_via_upload
250    basic_authorization "test@example.com", "test"
251    rel_id = current_relations(:multi_tag_relation).id
252    cs_id = changesets(:public_user_first_change).id
253
254    with_relation(rel_id) do |rel|
255      # alter one of the tags
256      tag = rel.find("//osm/relation/tag").first
257      tag['v'] = 'some changed value'
258      update_changeset(rel, cs_id)
259
260      # check that the downloaded tags are the same as the uploaded tags...
261      new_version = with_update_diff(rel) do |new_rel|
262        assert_tags_equal rel, new_rel
263      end
264
265      # check the original one in the current_* table again
266      with_relation(rel_id) { |r| assert_tags_equal rel, r }
267
268      # now check the version in the history
269      with_relation(rel_id, new_version) { |r| assert_tags_equal rel, r }
270    end
271  end
272
273  # -------------------------------------
274  # Test creating some invalid relations.
275  # -------------------------------------
276
277  def test_create_invalid
278    basic_authorization "test@openstreetmap.org", "test"
279
280    # put the relation in a dummy fixture changset
281    changeset_id = changesets(:normal_user_first_change).id
282
283    # create a relation with non-existing node as member
284    content "<osm><relation changeset='#{changeset_id}'>" +
285      "<member type='node' ref='0'/><tag k='test' v='yes' />" +
286      "</relation></osm>"
287    put :create
288    # expect failure
289    assert_response :precondition_failed, 
290        "relation upload with invalid node did not return 'precondition failed'"
291  end
292
293  # -------------------------------------
294  # Test creating a relation, with some invalid XML
295  # -------------------------------------
296  def test_create_invalid_xml
297    basic_authorization "test@openstreetmap.org", "test"
298   
299    # put the relation in a dummy fixture changeset that works
300    changeset_id = changesets(:normal_user_first_change).id
301   
302    # create some xml that should return an error
303    content "<osm><relation changeset='#{changeset_id}'>" +
304    "<member type='type' ref='#{current_nodes(:used_node_1).id}' role=''/>" +
305    "<tag k='tester' v='yep'/></relation></osm>"
306    put :create
307    # expect failure
308    assert_response :bad_request
309    assert_match(/Cannot parse valid relation from xml string/, @response.body)
310    assert_match(/The type is not allowed only, /, @response.body)
311  end
312 
313 
314  # -------------------------------------
315  # Test deleting relations.
316  # -------------------------------------
317 
318  def test_delete
319    # first try to delete relation without auth
320    delete :delete, :id => current_relations(:visible_relation).id
321    assert_response :unauthorized
322
323    # now set auth
324    basic_authorization("test@openstreetmap.org", "test"); 
325
326    # this shouldn't work, as we should need the payload...
327    delete :delete, :id => current_relations(:visible_relation).id
328    assert_response :bad_request
329
330    # try to delete without specifying a changeset
331    content "<osm><relation id='#{current_relations(:visible_relation).id}'/></osm>"
332    delete :delete, :id => current_relations(:visible_relation).id
333    assert_response :bad_request
334    assert_match(/You are missing the required changeset in the relation/, @response.body)
335
336    # try to delete with an invalid (closed) changeset
337    content update_changeset(current_relations(:visible_relation).to_xml,
338                             changesets(:normal_user_closed_change).id)
339    delete :delete, :id => current_relations(:visible_relation).id
340    assert_response :conflict
341
342    # try to delete with an invalid (non-existent) changeset
343    content update_changeset(current_relations(:visible_relation).to_xml,0)
344    delete :delete, :id => current_relations(:visible_relation).id
345    assert_response :conflict
346
347    # this won't work because the relation is in-use by another relation
348    content(relations(:used_relation).to_xml)
349    delete :delete, :id => current_relations(:used_relation).id
350    assert_response :precondition_failed, 
351       "shouldn't be able to delete a relation used in a relation (#{@response.body})"
352
353    # this should work when we provide the appropriate payload...
354    content(relations(:visible_relation).to_xml)
355    delete :delete, :id => current_relations(:visible_relation).id
356    assert_response :success
357
358    # valid delete should return the new version number, which should
359    # be greater than the old version number
360    assert @response.body.to_i > current_relations(:visible_relation).version,
361       "delete request should return a new version number for relation"
362
363    # this won't work since the relation is already deleted
364    content(relations(:invisible_relation).to_xml)
365    delete :delete, :id => current_relations(:invisible_relation).id
366    assert_response :gone
367
368    # this works now because the relation which was using this one
369    # has been deleted.
370    content(relations(:used_relation).to_xml)
371    delete :delete, :id => current_relations(:used_relation).id
372    assert_response :success, 
373       "should be able to delete a relation used in an old relation (#{@response.body})"
374
375    # this won't work since the relation never existed
376    delete :delete, :id => 0
377    assert_response :not_found
378  end
379
380  ##
381  # when a relation's tag is modified then it should put the bounding
382  # box of all its members into the changeset.
383  def test_tag_modify_bounding_box
384    # in current fixtures, relation 5 contains nodes 3 and 5 (node 3
385    # indirectly via way 3), so the bbox should be [3,3,5,5].
386    check_changeset_modify([3,3,5,5]) do |changeset_id|
387      # add a tag to an existing relation
388      relation_xml = current_relations(:visible_relation).to_xml
389      relation_element = relation_xml.find("//osm/relation").first
390      new_tag = XML::Node.new("tag")
391      new_tag['k'] = "some_new_tag"
392      new_tag['v'] = "some_new_value"
393      relation_element << new_tag
394     
395      # update changeset ID to point to new changeset
396      update_changeset(relation_xml, changeset_id)
397     
398      # upload the change
399      content relation_xml
400      put :update, :id => current_relations(:visible_relation).id
401      assert_response :success, "can't update relation for tag/bbox test"
402    end
403  end
404
405  ##
406  # add a member to a relation and check the bounding box is only that
407  # element.
408  def test_add_member_bounding_box
409    check_changeset_modify([4,4,4,4]) do |changeset_id|
410      # add node 4 (4,4) to an existing relation
411      relation_xml = current_relations(:visible_relation).to_xml
412      relation_element = relation_xml.find("//osm/relation").first
413      new_member = XML::Node.new("member")
414      new_member['ref'] = current_nodes(:used_node_2).id.to_s
415      new_member['type'] = "node"
416      new_member['role'] = "some_role"
417      relation_element << new_member
418     
419      # update changeset ID to point to new changeset
420      update_changeset(relation_xml, changeset_id)
421     
422      # upload the change
423      content relation_xml
424      put :update, :id => current_relations(:visible_relation).id
425      assert_response :success, "can't update relation for add node/bbox test"
426    end
427  end
428 
429  ##
430  # remove a member from a relation and check the bounding box is
431  # only that element.
432  def test_remove_member_bounding_box
433    check_changeset_modify([5,5,5,5]) do |changeset_id|
434      # remove node 5 (5,5) from an existing relation
435      relation_xml = current_relations(:visible_relation).to_xml
436      relation_xml.
437        find("//osm/relation/member[@type='node'][@ref='5']").
438        first.remove!
439     
440      # update changeset ID to point to new changeset
441      update_changeset(relation_xml, changeset_id)
442     
443      # upload the change
444      content relation_xml
445      put :update, :id => current_relations(:visible_relation).id
446      assert_response :success, "can't update relation for remove node/bbox test"
447    end
448  end
449 
450  ##
451  # check that relations are ordered
452  def test_relation_member_ordering
453    basic_authorization("test@openstreetmap.org", "test"); 
454
455    doc_str = <<OSM
456<osm>
457 <relation changeset='1'>
458  <member ref='1' type='node' role='first'/>
459  <member ref='3' type='node' role='second'/>
460  <member ref='1' type='way' role='third'/>
461  <member ref='3' type='way' role='fourth'/>
462 </relation>
463</osm>
464OSM
465    doc = XML::Parser.string(doc_str).parse
466
467    content doc
468    put :create
469    assert_response :success, "can't create a relation: #{@response.body}"
470    relation_id = @response.body.to_i
471
472    # get it back and check the ordering
473    get :read, :id => relation_id
474    assert_response :success, "can't read back the relation: #{@response.body}"
475    check_ordering(doc, @response.body)
476
477    # insert a member at the front
478    new_member = XML::Node.new "member"
479    new_member['ref'] = 5.to_s
480    new_member['type'] = 'node'
481    new_member['role'] = 'new first'
482    doc.find("//osm/relation").first.child.prev = new_member
483    # update the version, should be 1?
484    doc.find("//osm/relation").first['id'] = relation_id.to_s
485    doc.find("//osm/relation").first['version'] = 1.to_s
486
487    # upload the next version of the relation
488    content doc
489    put :update, :id => relation_id
490    assert_response :success, "can't update relation: #{@response.body}"
491    new_version = @response.body.to_i
492
493    # get it back again and check the ordering again
494    get :read, :id => relation_id
495    assert_response :success, "can't read back the relation: #{@response.body}"
496    check_ordering(doc, @response.body)
497  end
498
499  ##
500  # check that relations can contain duplicate members
501  def test_relation_member_duplicates
502    basic_authorization("test@openstreetmap.org", "test"); 
503
504    doc_str = <<OSM
505<osm>
506 <relation changeset='1'>
507  <member ref='1' type='node' role='forward'/>
508  <member ref='3' type='node' role='forward'/>
509  <member ref='1' type='node' role='forward'/>
510  <member ref='3' type='node' role='forward'/>
511 </relation>
512</osm>
513OSM
514    doc = XML::Parser.string(doc_str).parse
515
516    content doc
517    put :create
518    assert_response :success, "can't create a relation: #{@response.body}"
519    relation_id = @response.body.to_i
520
521    # get it back and check the ordering
522    get :read, :id => relation_id
523    assert_response :success, "can't read back the relation: #{@response.body}"
524    check_ordering(doc, @response.body)
525  end
526
527  # ============================================================
528  # utility functions
529  # ============================================================
530
531  ##
532  # checks that the XML document and the string arguments have
533  # members in the same order.
534  def check_ordering(doc, xml)
535    new_doc = XML::Parser.string(xml).parse
536
537    doc_members = doc.find("//osm/relation/member").collect do |m|
538      [m['ref'].to_i, m['type'].to_sym, m['role']]
539    end
540
541    new_members = new_doc.find("//osm/relation/member").collect do |m|
542      [m['ref'].to_i, m['type'].to_sym, m['role']]
543    end
544
545    doc_members.zip(new_members).each do |d, n|
546      assert_equal d, n, "members are not equal - ordering is wrong? (#{doc}, #{xml})"
547    end
548  end
549
550  ##
551  # create a changeset and yield to the caller to set it up, then assert
552  # that the changeset bounding box is +bbox+.
553  def check_changeset_modify(bbox)
554    basic_authorization("test@openstreetmap.org", "test"); 
555
556    # create a new changeset for this operation, so we are assured
557    # that the bounding box will be newly-generated.
558    changeset_id = with_controller(ChangesetController.new) do
559      content "<osm><changeset/></osm>"
560      put :create
561      assert_response :success, "couldn't create changeset for modify test"
562      @response.body.to_i
563    end
564
565    # go back to the block to do the actual modifies
566    yield changeset_id
567
568    # now download the changeset to check its bounding box
569    with_controller(ChangesetController.new) do
570      get :read, :id => changeset_id
571      assert_response :success, "can't re-read changeset for modify test"
572      assert_select "osm>changeset", 1
573      assert_select "osm>changeset[id=#{changeset_id}]", 1
574      assert_select "osm>changeset[min_lon=#{bbox[0].to_f}]", 1
575      assert_select "osm>changeset[min_lat=#{bbox[1].to_f}]", 1
576      assert_select "osm>changeset[max_lon=#{bbox[2].to_f}]", 1
577      assert_select "osm>changeset[max_lat=#{bbox[3].to_f}]", 1
578    end
579  end
580
581  ##
582  # yields the relation with the given +id+ (and optional +version+
583  # to read from the history tables) into the block. the parsed XML
584  # doc is returned.
585  def with_relation(id, ver = nil)
586    if ver.nil?
587      get :read, :id => id
588    else
589      with_controller(OldRelationController.new) do
590        get :version, :id => id, :version => ver
591      end
592    end
593    assert_response :success
594    yield xml_parse(@response.body)
595  end
596
597  ##
598  # updates the relation (XML) +rel+ and
599  # yields the new version of that relation into the block.
600  # the parsed XML doc is retured.
601  def with_update(rel)
602    rel_id = rel.find("//osm/relation").first["id"].to_i
603    content rel
604    put :update, :id => rel_id
605    assert_response :success, "can't update relation: #{@response.body}"
606    version = @response.body.to_i
607
608    # now get the new version
609    get :read, :id => rel_id
610    assert_response :success
611    new_rel = xml_parse(@response.body)
612
613    yield new_rel
614
615    return version
616  end
617
618  ##
619  # updates the relation (XML) +rel+ via the diff-upload API and
620  # yields the new version of that relation into the block.
621  # the parsed XML doc is retured.
622  def with_update_diff(rel)
623    rel_id = rel.find("//osm/relation").first["id"].to_i
624    cs_id = rel.find("//osm/relation").first['changeset'].to_i
625    version = nil
626
627    with_controller(ChangesetController.new) do
628      doc = OSM::API.new.get_xml_doc
629      change = XML::Node.new 'osmChange'
630      doc.root = change
631      modify = XML::Node.new 'modify'
632      change << modify
633      modify << doc.import(rel.find("//osm/relation").first)
634
635      content doc.to_s
636      post :upload, :id => cs_id
637      assert_response :success, "can't upload diff relation: #{@response.body}"
638      version = xml_parse(@response.body).find("//diffResult/relation").first["new_version"].to_i
639    end     
640   
641    # now get the new version
642    get :read, :id => rel_id
643    assert_response :success
644    new_rel = xml_parse(@response.body)
645   
646    yield new_rel
647   
648    return version
649  end
650
651  ##
652  # returns a k->v hash of tags from an xml doc
653  def get_tags_as_hash(a) 
654    a.find("//osm/relation/tag").inject({}) do |h,v|
655      h[v['k']] = v['v']
656      h
657    end
658  end
659 
660  ##
661  # assert that all tags on relation documents +a+ and +b+
662  # are equal
663  def assert_tags_equal(a, b)
664    # turn the XML doc into tags hashes
665    a_tags = get_tags_as_hash(a)
666    b_tags = get_tags_as_hash(b)
667
668    assert_equal a_tags.keys, b_tags.keys, "Tag keys should be identical."
669    a_tags.each do |k, v|
670      assert_equal v, b_tags[k], 
671        "Tags which were not altered should be the same. " +
672        "#{a_tags.inspect} != #{b_tags.inspect}"
673    end
674  end
675
676  ##
677  # update the changeset_id of a node element
678  def update_changeset(xml, changeset_id)
679    xml_attr_rewrite(xml, 'changeset', changeset_id)
680  end
681
682  ##
683  # update an attribute in the node element
684  def xml_attr_rewrite(xml, name, value)
685    xml.find("//osm/relation").first[name] = value.to_s
686    return xml
687  end
688
689  ##
690  # parse some xml
691  def xml_parse(xml)
692    parser = XML::Parser.string(xml)
693    parser.parse
694  end
695end
Note: See TracBrowser for help on using the repository browser.