]> git.openstreetmap.org Git - rails.git/blob - test/functional/relation_controller_test.rb
Wrap XML notes responses in an <osm> wrapper
[rails.git] / test / functional / relation_controller_test.rb
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'relation_controller'
3
4 class RelationControllerTest < ActionController::TestCase
5   api_fixtures
6
7   ##
8   # test all routes which lead to this controller
9   def test_routes
10     assert_routing(
11       { :path => "/api/0.6/relation/create", :method => :put },
12       { :controller => "relation", :action => "create" }
13     )
14     assert_routing(
15       { :path => "/api/0.6/relation/1/full", :method => :get },
16       { :controller => "relation", :action => "full", :id => "1" }
17     )
18     assert_routing(
19       { :path => "/api/0.6/relation/1", :method => :get },
20       { :controller => "relation", :action => "read", :id => "1" }
21     )
22     assert_routing(
23       { :path => "/api/0.6/relation/1", :method => :put },
24       { :controller => "relation", :action => "update", :id => "1" }
25     )
26     assert_routing(
27       { :path => "/api/0.6/relation/1", :method => :delete },
28       { :controller => "relation", :action => "delete", :id => "1" }
29     )
30     assert_routing(
31       { :path => "/api/0.6/relations", :method => :get },
32       { :controller => "relation", :action => "relations" }
33     )
34
35     assert_routing(
36       { :path => "/api/0.6/node/1/relations", :method => :get },
37       { :controller => "relation", :action => "relations_for_node", :id => "1" }
38     )
39     assert_routing(
40       { :path => "/api/0.6/way/1/relations", :method => :get },
41       { :controller => "relation", :action => "relations_for_way", :id => "1" }
42     )
43     assert_routing(
44       { :path => "/api/0.6/relation/1/relations", :method => :get },
45       { :controller => "relation", :action => "relations_for_relation", :id => "1" }
46     )
47   end
48
49   # -------------------------------------
50   # Test reading relations.
51   # -------------------------------------
52
53   def test_read
54     # check that a visible relation is returned properly
55     get :read, :id => current_relations(:visible_relation).id
56     assert_response :success
57
58     # check that an invisible relation is not returned
59     get :read, :id => current_relations(:invisible_relation).id
60     assert_response :gone
61
62     # check chat a non-existent relation is not returned
63     get :read, :id => 0
64     assert_response :not_found
65   end
66
67   ##
68   # check that all relations containing a particular node, and no extra
69   # relations, are returned from the relations_for_node call.
70   def test_relations_for_node
71     check_relations_for_element(:relations_for_node, "node", 
72                                 current_nodes(:node_used_by_relationship).id,
73                                 [ :visible_relation, :used_relation ])
74   end
75
76   def test_relations_for_way
77     check_relations_for_element(:relations_for_way, "way",
78                                 current_ways(:used_way).id,
79                                 [ :visible_relation ])
80   end
81
82   def test_relations_for_relation
83     check_relations_for_element(:relations_for_relation, "relation",
84                                 current_relations(:used_relation).id,
85                                 [ :visible_relation ])
86   end
87
88   def check_relations_for_element(method, type, id, expected_relations)
89     # check the "relations for relation" mode
90     get method, :id => id
91     assert_response :success
92
93     # count one osm element
94     assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
95
96     # we should have only the expected number of relations
97     assert_select "osm>relation", expected_relations.size
98
99     # and each of them should contain the node we originally searched for
100     expected_relations.each do |r|
101       relation_id = current_relations(r).id
102       assert_select "osm>relation#?", relation_id
103       assert_select "osm>relation#?>member[type=\"#{type}\"][ref=#{id}]", relation_id
104     end
105   end
106
107   def test_full
108     # check the "full" mode
109     get :full, :id => current_relations(:visible_relation).id
110     assert_response :success
111     # FIXME check whether this contains the stuff we want!
112     if $VERBOSE
113         print @response.body
114     end
115   end
116
117   # -------------------------------------
118   # Test simple relation creation.
119   # -------------------------------------
120
121   def test_create
122     basic_authorization users(:normal_user).email, "test"
123     
124     # put the relation in a dummy fixture changset
125     changeset_id = changesets(:normal_user_first_change).id
126
127     # create an relation without members
128     content "<osm><relation changeset='#{changeset_id}'><tag k='test' v='yes' /></relation></osm>"
129     put :create
130     # hope for forbidden, due to user
131     assert_response :forbidden, 
132     "relation upload should have failed with forbidden"
133
134     ###
135     # create an relation with a node as member
136     # This time try with a role attribute in the relation
137     nid = current_nodes(:used_node_1).id
138     content "<osm><relation changeset='#{changeset_id}'>" +
139       "<member  ref='#{nid}' type='node' role='some'/>" +
140       "<tag k='test' v='yes' /></relation></osm>"
141     put :create
142     # hope for forbidden due to user
143     assert_response :forbidden, 
144     "relation upload did not return forbidden status"
145     
146     ###
147     # create an relation with a node as member, this time test that we don't 
148     # need a role attribute to be included
149     nid = current_nodes(:used_node_1).id
150     content "<osm><relation changeset='#{changeset_id}'>" +
151       "<member  ref='#{nid}' type='node'/>"+
152       "<tag k='test' v='yes' /></relation></osm>"
153     put :create
154     # hope for forbidden due to user
155     assert_response :forbidden, 
156     "relation upload did not return forbidden status"
157
158     ###
159     # create an relation with a way and a node as members
160     nid = current_nodes(:used_node_1).id
161     wid = current_ways(:used_way).id
162     content "<osm><relation changeset='#{changeset_id}'>" +
163       "<member type='node' ref='#{nid}' role='some'/>" +
164       "<member type='way' ref='#{wid}' role='other'/>" +
165       "<tag k='test' v='yes' /></relation></osm>"
166     put :create
167     # hope for forbidden, due to user
168     assert_response :forbidden, 
169         "relation upload did not return success status"
170
171
172
173     ## Now try with the public user
174     basic_authorization users(:public_user).email, "test"
175     
176     # put the relation in a dummy fixture changset
177     changeset_id = changesets(:public_user_first_change).id
178
179     # create an relation without members
180     content "<osm><relation changeset='#{changeset_id}'><tag k='test' v='yes' /></relation></osm>"
181     put :create
182     # hope for success
183     assert_response :success, 
184         "relation upload did not return success status"
185     # read id of created relation and search for it
186     relationid = @response.body
187     checkrelation = Relation.find(relationid)
188     assert_not_nil checkrelation, 
189         "uploaded relation not found in data base after upload"
190     # compare values
191     assert_equal checkrelation.members.length, 0, 
192         "saved relation contains members but should not"
193     assert_equal checkrelation.tags.length, 1, 
194         "saved relation does not contain exactly one tag"
195     assert_equal changeset_id, checkrelation.changeset.id,
196         "saved relation does not belong in the changeset it was assigned to"
197     assert_equal users(:public_user).id, checkrelation.changeset.user_id, 
198         "saved relation does not belong to user that created it"
199     assert_equal true, checkrelation.visible, 
200         "saved relation is not visible"
201     # ok the relation is there but can we also retrieve it?
202     get :read, :id => relationid
203     assert_response :success
204
205
206     ###
207     # create an relation with a node as member
208     # This time try with a role attribute in the relation
209     nid = current_nodes(:used_node_1).id
210     content "<osm><relation changeset='#{changeset_id}'>" +
211       "<member  ref='#{nid}' type='node' role='some'/>" +
212       "<tag k='test' v='yes' /></relation></osm>"
213     put :create
214     # hope for success
215     assert_response :success, 
216         "relation upload did not return success status"
217     # read id of created relation and search for it
218     relationid = @response.body
219     checkrelation = Relation.find(relationid)
220     assert_not_nil checkrelation, 
221         "uploaded relation not found in data base after upload"
222     # compare values
223     assert_equal checkrelation.members.length, 1, 
224         "saved relation does not contain exactly one member"
225     assert_equal checkrelation.tags.length, 1, 
226         "saved relation does not contain exactly one tag"
227     assert_equal changeset_id, checkrelation.changeset.id,
228         "saved relation does not belong in the changeset it was assigned to"
229     assert_equal users(:public_user).id, checkrelation.changeset.user_id, 
230         "saved relation does not belong to user that created it"
231     assert_equal true, checkrelation.visible, 
232         "saved relation is not visible"
233     # ok the relation is there but can we also retrieve it?
234     
235     get :read, :id => relationid
236     assert_response :success
237     
238     
239     ###
240     # create an relation with a node as member, this time test that we don't 
241     # need a role attribute to be included
242     nid = current_nodes(:used_node_1).id
243     content "<osm><relation changeset='#{changeset_id}'>" +
244       "<member  ref='#{nid}' type='node'/>"+
245       "<tag k='test' v='yes' /></relation></osm>"
246     put :create
247     # hope for success
248     assert_response :success, 
249         "relation upload did not return success status"
250     # read id of created relation and search for it
251     relationid = @response.body
252     checkrelation = Relation.find(relationid)
253     assert_not_nil checkrelation, 
254         "uploaded relation not found in data base after upload"
255     # compare values
256     assert_equal checkrelation.members.length, 1, 
257         "saved relation does not contain exactly one member"
258     assert_equal checkrelation.tags.length, 1, 
259         "saved relation does not contain exactly one tag"
260     assert_equal changeset_id, checkrelation.changeset.id,
261         "saved relation does not belong in the changeset it was assigned to"
262     assert_equal users(:public_user).id, checkrelation.changeset.user_id, 
263         "saved relation does not belong to user that created it"
264     assert_equal true, checkrelation.visible, 
265         "saved relation is not visible"
266     # ok the relation is there but can we also retrieve it?
267     
268     get :read, :id => relationid
269     assert_response :success
270
271     ###
272     # create an relation with a way and a node as members
273     nid = current_nodes(:used_node_1).id
274     wid = current_ways(:used_way).id
275     content "<osm><relation changeset='#{changeset_id}'>" +
276       "<member type='node' ref='#{nid}' role='some'/>" +
277       "<member type='way' ref='#{wid}' role='other'/>" +
278       "<tag k='test' v='yes' /></relation></osm>"
279     put :create
280     # hope for success
281     assert_response :success, 
282         "relation upload did not return success status"
283     # read id of created relation and search for it
284     relationid = @response.body
285     checkrelation = Relation.find(relationid)
286     assert_not_nil checkrelation, 
287         "uploaded relation not found in data base after upload"
288     # compare values
289     assert_equal checkrelation.members.length, 2, 
290         "saved relation does not have exactly two members"
291     assert_equal checkrelation.tags.length, 1, 
292         "saved relation does not contain exactly one tag"
293     assert_equal changeset_id, checkrelation.changeset.id,
294         "saved relation does not belong in the changeset it was assigned to"
295     assert_equal users(:public_user).id, checkrelation.changeset.user_id, 
296         "saved relation does not belong to user that created it"
297     assert_equal true, checkrelation.visible, 
298         "saved relation is not visible"
299     # ok the relation is there but can we also retrieve it?
300     get :read, :id => relationid
301     assert_response :success
302
303   end
304
305   # ------------------------------------
306   # Test updating relations
307   # ------------------------------------
308
309   ##
310   # test that, when tags are updated on a relation, the correct things
311   # happen to the correct tables and the API gives sensible results. 
312   # this is to test a case that gregory marler noticed and posted to
313   # josm-dev.
314   ## FIXME Move this to an integration test
315   def test_update_relation_tags
316     basic_authorization "test@example.com", "test"
317     rel_id = current_relations(:multi_tag_relation).id
318     cs_id = changesets(:public_user_first_change).id
319
320     with_relation(rel_id) do |rel|
321       # alter one of the tags
322       tag = rel.find("//osm/relation/tag").first
323       tag['v'] = 'some changed value'
324       update_changeset(rel, cs_id)
325
326       # check that the downloaded tags are the same as the uploaded tags...
327       new_version = with_update(rel) do |new_rel|
328         assert_tags_equal rel, new_rel
329       end
330
331       # check the original one in the current_* table again
332       with_relation(rel_id) { |r| assert_tags_equal rel, r }
333
334       # now check the version in the history
335       with_relation(rel_id, new_version) { |r| assert_tags_equal rel, r }
336     end
337   end
338
339   ##
340   # test that, when tags are updated on a relation when using the diff
341   # upload function, the correct things happen to the correct tables 
342   # and the API gives sensible results. this is to test a case that 
343   # gregory marler noticed and posted to josm-dev.
344   def test_update_relation_tags_via_upload
345     basic_authorization users(:public_user).email, "test"
346     rel_id = current_relations(:multi_tag_relation).id
347     cs_id = changesets(:public_user_first_change).id
348
349     with_relation(rel_id) do |rel|
350       # alter one of the tags
351       tag = rel.find("//osm/relation/tag").first
352       tag['v'] = 'some changed value'
353       update_changeset(rel, cs_id)
354
355       # check that the downloaded tags are the same as the uploaded tags...
356       new_version = with_update_diff(rel) do |new_rel|
357         assert_tags_equal rel, new_rel
358       end
359
360       # check the original one in the current_* table again
361       with_relation(rel_id) { |r| assert_tags_equal rel, r }
362
363       # now check the version in the history
364       with_relation(rel_id, new_version) { |r| assert_tags_equal rel, r }
365     end
366   end
367
368   # -------------------------------------
369   # Test creating some invalid relations.
370   # -------------------------------------
371
372   def test_create_invalid
373     basic_authorization users(:public_user).email, "test"
374
375     # put the relation in a dummy fixture changset
376     changeset_id = changesets(:public_user_first_change).id
377
378     # create a relation with non-existing node as member
379     content "<osm><relation changeset='#{changeset_id}'>" +
380       "<member type='node' ref='0'/><tag k='test' v='yes' />" +
381       "</relation></osm>"
382     put :create
383     # expect failure
384     assert_response :precondition_failed, 
385         "relation upload with invalid node did not return 'precondition failed'"
386     assert_equal "Precondition failed: Relation with id  cannot be saved due to Node with id 0", @response.body
387   end
388
389   # -------------------------------------
390   # Test creating a relation, with some invalid XML
391   # -------------------------------------
392   def test_create_invalid_xml
393     basic_authorization users(:public_user).email, "test"
394     
395     # put the relation in a dummy fixture changeset that works
396     changeset_id = changesets(:public_user_first_change).id
397     
398     # create some xml that should return an error
399     content "<osm><relation changeset='#{changeset_id}'>" +
400     "<member type='type' ref='#{current_nodes(:used_node_1).id}' role=''/>" +
401     "<tag k='tester' v='yep'/></relation></osm>"
402     put :create
403     # expect failure
404     assert_response :bad_request
405     assert_match(/Cannot parse valid relation from xml string/, @response.body)
406     assert_match(/The type is not allowed only, /, @response.body)
407   end
408   
409   
410   # -------------------------------------
411   # Test deleting relations.
412   # -------------------------------------
413   
414   def test_delete
415     ## First try to delete relation without auth
416     delete :delete, :id => current_relations(:visible_relation).id
417     assert_response :unauthorized
418     
419     
420     ## Then try with the private user, to make sure that you get a forbidden
421     basic_authorization(users(:normal_user).email, "test")
422     
423     # this shouldn't work, as we should need the payload...
424     delete :delete, :id => current_relations(:visible_relation).id
425     assert_response :forbidden
426
427     # try to delete without specifying a changeset
428     content "<osm><relation id='#{current_relations(:visible_relation).id}'/></osm>"
429     delete :delete, :id => current_relations(:visible_relation).id
430     assert_response :forbidden
431
432     # try to delete with an invalid (closed) changeset
433     content update_changeset(current_relations(:visible_relation).to_xml,
434                              changesets(:normal_user_closed_change).id)
435     delete :delete, :id => current_relations(:visible_relation).id
436     assert_response :forbidden
437
438     # try to delete with an invalid (non-existent) changeset
439     content update_changeset(current_relations(:visible_relation).to_xml,0)
440     delete :delete, :id => current_relations(:visible_relation).id
441     assert_response :forbidden
442
443     # this won't work because the relation is in-use by another relation
444     content(relations(:used_relation).to_xml)
445     delete :delete, :id => current_relations(:used_relation).id
446     assert_response :forbidden
447
448     # this should work when we provide the appropriate payload...
449     content(relations(:visible_relation).to_xml)
450     delete :delete, :id => current_relations(:visible_relation).id
451     assert_response :forbidden
452
453     # this won't work since the relation is already deleted
454     content(relations(:invisible_relation).to_xml)
455     delete :delete, :id => current_relations(:invisible_relation).id
456     assert_response :forbidden
457
458     # this works now because the relation which was using this one 
459     # has been deleted.
460     content(relations(:used_relation).to_xml)
461     delete :delete, :id => current_relations(:used_relation).id
462     assert_response :forbidden
463
464     # this won't work since the relation never existed
465     delete :delete, :id => 0
466     assert_response :forbidden
467
468     
469
470     ## now set auth for the public user
471     basic_authorization(users(:public_user).email, "test");  
472
473     # this shouldn't work, as we should need the payload...
474     delete :delete, :id => current_relations(:visible_relation).id
475     assert_response :bad_request
476
477     # try to delete without specifying a changeset
478     content "<osm><relation id='#{current_relations(:visible_relation).id}' version='#{current_relations(:visible_relation).version}' /></osm>"
479     delete :delete, :id => current_relations(:visible_relation).id
480     assert_response :bad_request
481     assert_match(/Changeset id is missing/, @response.body)
482
483     # try to delete with an invalid (closed) changeset
484     content update_changeset(current_relations(:visible_relation).to_xml,
485                              changesets(:normal_user_closed_change).id)
486     delete :delete, :id => current_relations(:visible_relation).id
487     assert_response :conflict
488
489     # try to delete with an invalid (non-existent) changeset
490     content update_changeset(current_relations(:visible_relation).to_xml,0)
491     delete :delete, :id => current_relations(:visible_relation).id
492     assert_response :conflict
493
494     # this won't work because the relation is in a changeset owned by someone else
495     content(relations(:used_relation).to_xml)
496     delete :delete, :id => current_relations(:used_relation).id
497     assert_response :conflict, 
498     "shouldn't be able to delete a relation in a changeset owned by someone else (#{@response.body})"
499
500     # this won't work because the relation in the payload is different to that passed
501     content(relations(:public_used_relation).to_xml)
502     delete :delete, :id => current_relations(:used_relation).id
503     assert_not_equal relations(:public_used_relation).id, current_relations(:used_relation).id
504     assert_response :bad_request, "shouldn't be able to delete a relation when payload is different to the url"
505     
506     # this won't work because the relation is in-use by another relation
507     content(relations(:public_used_relation).to_xml)
508     delete :delete, :id => current_relations(:public_used_relation).id
509     assert_response :precondition_failed, 
510        "shouldn't be able to delete a relation used in a relation (#{@response.body})"
511     assert_equal "Precondition failed: The relation 5 is used in relation 6.", @response.body
512
513     # this should work when we provide the appropriate payload...
514     content(relations(:multi_tag_relation).to_xml)
515     delete :delete, :id => current_relations(:multi_tag_relation).id
516     assert_response :success
517
518     # valid delete should return the new version number, which should
519     # be greater than the old version number
520     assert @response.body.to_i > current_relations(:visible_relation).version,
521        "delete request should return a new version number for relation"
522
523     # this won't work since the relation is already deleted
524     content(relations(:invisible_relation).to_xml)
525     delete :delete, :id => current_relations(:invisible_relation).id
526     assert_response :gone
527     
528     # Public visible relation needs to be deleted
529     content(relations(:public_visible_relation).to_xml)
530     delete :delete, :id => current_relations(:public_visible_relation).id
531     assert_response :success
532
533     # this works now because the relation which was using this one 
534     # has been deleted.
535     content(relations(:public_used_relation).to_xml)
536     delete :delete, :id => current_relations(:public_used_relation).id
537     assert_response :success, 
538        "should be able to delete a relation used in an old relation (#{@response.body})"
539
540     # this won't work since the relation never existed
541     delete :delete, :id => 0
542     assert_response :not_found
543   end
544
545   ##
546   # when a relation's tag is modified then it should put the bounding
547   # box of all its members into the changeset.
548   def test_tag_modify_bounding_box
549     # in current fixtures, relation 5 contains nodes 3 and 5 (node 3
550     # indirectly via way 3), so the bbox should be [3,3,5,5].
551     check_changeset_modify(BoundingBox.new(3,3,5,5)) do |changeset_id|
552       # add a tag to an existing relation
553       relation_xml = current_relations(:visible_relation).to_xml
554       relation_element = relation_xml.find("//osm/relation").first
555       new_tag = XML::Node.new("tag")
556       new_tag['k'] = "some_new_tag"
557       new_tag['v'] = "some_new_value"
558       relation_element << new_tag
559       
560       # update changeset ID to point to new changeset
561       update_changeset(relation_xml, changeset_id)
562       
563       # upload the change
564       content relation_xml
565       put :update, :id => current_relations(:visible_relation).id
566       assert_response :success, "can't update relation for tag/bbox test"
567     end
568   end
569
570   ##
571   # add a member to a relation and check the bounding box is only that
572   # element.
573   def test_add_member_bounding_box
574     relation_id = current_relations(:visible_relation).id
575
576     [current_nodes(:used_node_1),
577      current_nodes(:used_node_2),
578      current_ways(:used_way),
579      current_ways(:way_with_versions)
580     ].each_with_index do |element, version|
581       bbox = element.bbox.to_unscaled
582       check_changeset_modify(bbox) do |changeset_id|
583         relation_xml = Relation.find(relation_id).to_xml
584         relation_element = relation_xml.find("//osm/relation").first
585         new_member = XML::Node.new("member")
586         new_member['ref'] = element.id.to_s
587         new_member['type'] = element.class.to_s.downcase
588         new_member['role'] = "some_role"
589         relation_element << new_member
590       
591         # update changeset ID to point to new changeset
592         update_changeset(relation_xml, changeset_id)
593       
594         # upload the change
595         content relation_xml
596         put :update, :id => current_relations(:visible_relation).id
597         assert_response :success, "can't update relation for add #{element.class}/bbox test: #{@response.body}"
598
599         # get it back and check the ordering 
600         get :read, :id => relation_id
601         assert_response :success, "can't read back the relation: #{@response.body}"
602         check_ordering(relation_xml, @response.body)
603       end
604     end
605   end
606   
607   ##
608   # remove a member from a relation and check the bounding box is 
609   # only that element.
610   def test_remove_member_bounding_box
611     check_changeset_modify(BoundingBox.new(5,5,5,5)) do |changeset_id|
612       # remove node 5 (5,5) from an existing relation
613       relation_xml = current_relations(:visible_relation).to_xml
614       relation_xml.
615         find("//osm/relation/member[@type='node'][@ref='5']").
616         first.remove!
617       
618       # update changeset ID to point to new changeset
619       update_changeset(relation_xml, changeset_id)
620       
621       # upload the change
622       content relation_xml
623       put :update, :id => current_relations(:visible_relation).id
624       assert_response :success, "can't update relation for remove node/bbox test"
625     end
626   end
627   
628   ##
629   # check that relations are ordered
630   def test_relation_member_ordering
631     basic_authorization(users(:public_user).email, "test")
632     
633     doc_str = <<OSM
634 <osm>
635  <relation changeset='4'>
636   <member ref='1' type='node' role='first'/>
637   <member ref='3' type='node' role='second'/>
638   <member ref='1' type='way' role='third'/>
639   <member ref='3' type='way' role='fourth'/>
640  </relation>
641 </osm>
642 OSM
643     doc = XML::Parser.string(doc_str).parse
644
645     content doc
646     put :create
647     assert_response :success, "can't create a relation: #{@response.body}"
648     relation_id = @response.body.to_i
649
650     # get it back and check the ordering
651     get :read, :id => relation_id
652     assert_response :success, "can't read back the relation: #{@response.body}"
653     check_ordering(doc, @response.body)
654
655     # insert a member at the front
656     new_member = XML::Node.new "member"
657     new_member['ref'] = 5.to_s
658     new_member['type'] = 'node'
659     new_member['role'] = 'new first'
660     doc.find("//osm/relation").first.child.prev = new_member
661     # update the version, should be 1?
662     doc.find("//osm/relation").first['id'] = relation_id.to_s
663     doc.find("//osm/relation").first['version'] = 1.to_s
664
665     # upload the next version of the relation
666     content doc
667     put :update, :id => relation_id
668     assert_response :success, "can't update relation: #{@response.body}"
669     new_version = @response.body.to_i
670
671     # get it back again and check the ordering again
672     get :read, :id => relation_id
673     assert_response :success, "can't read back the relation: #{@response.body}"
674     check_ordering(doc, @response.body)
675
676     # check the ordering in the history tables:
677     with_controller(OldRelationController.new) do
678       get :version, :id => relation_id, :version => 2
679       assert_response :success, "can't read back version 2 of the relation #{relation_id}"
680       check_ordering(doc, @response.body)
681     end
682   end
683
684   ## 
685   # check that relations can contain duplicate members
686   def test_relation_member_duplicates
687     doc_str = <<OSM
688 <osm>
689  <relation changeset='4'>
690   <member ref='1' type='node' role='forward'/>
691   <member ref='3' type='node' role='forward'/>
692   <member ref='1' type='node' role='forward'/>
693   <member ref='3' type='node' role='forward'/>
694  </relation>
695 </osm>
696 OSM
697     doc = XML::Parser.string(doc_str).parse
698
699     ## First try with the private user
700     basic_authorization(users(:normal_user).email, "test");  
701
702     content doc
703     put :create
704     assert_response :forbidden
705
706     ## Now try with the public user
707     basic_authorization(users(:public_user).email, "test");  
708
709     content doc
710     put :create
711     assert_response :success, "can't create a relation: #{@response.body}"
712     relation_id = @response.body.to_i
713
714     # get it back and check the ordering
715     get :read, :id => relation_id
716     assert_response :success, "can't read back the relation: #{relation_id}"
717     check_ordering(doc, @response.body)
718   end
719
720   ##
721   # test that the ordering of elements in the history is the same as in current.
722   def test_history_ordering
723     doc_str = <<OSM
724 <osm>
725  <relation changeset='4'>
726   <member ref='1' type='node' role='forward'/>
727   <member ref='5' type='node' role='forward'/>
728   <member ref='4' type='node' role='forward'/>
729   <member ref='3' type='node' role='forward'/>
730  </relation>
731 </osm>
732 OSM
733     doc = XML::Parser.string(doc_str).parse
734     basic_authorization(users(:public_user).email, "test");  
735
736     content doc
737     put :create
738     assert_response :success, "can't create a relation: #{@response.body}"
739     relation_id = @response.body.to_i
740
741     # check the ordering in the current tables:
742     get :read, :id => relation_id
743     assert_response :success, "can't read back the relation: #{@response.body}"
744     check_ordering(doc, @response.body)
745
746     # check the ordering in the history tables:
747     with_controller(OldRelationController.new) do
748       get :version, :id => relation_id, :version => 1
749       assert_response :success, "can't read back version 1 of the relation: #{@response.body}"
750       check_ordering(doc, @response.body)
751     end
752   end
753
754   ##
755   # remove all the members from a relation. the result is pretty useless, but
756   # still technically valid.
757   def test_remove_all_members 
758     check_changeset_modify(BoundingBox.new(3,3,5,5)) do |changeset_id|
759       relation_xml = current_relations(:visible_relation).to_xml
760       relation_xml.
761         find("//osm/relation/member").
762         each {|m| m.remove!}
763       
764       # update changeset ID to point to new changeset
765       update_changeset(relation_xml, changeset_id)
766       
767       # upload the change
768       content relation_xml
769       put :update, :id => current_relations(:visible_relation).id
770       assert_response :success, "can't update relation for remove all members test"
771       checkrelation = Relation.find(current_relations(:visible_relation).id)
772       assert_not_nil(checkrelation, 
773                      "uploaded relation not found in database after upload")
774       assert_equal(0, checkrelation.members.length,
775                    "relation contains members but they should have all been deleted")
776     end
777   end
778   
779   # ============================================================
780   # utility functions
781   # ============================================================
782
783   ##
784   # checks that the XML document and the string arguments have
785   # members in the same order.
786   def check_ordering(doc, xml)
787     new_doc = XML::Parser.string(xml).parse
788
789     doc_members = doc.find("//osm/relation/member").collect do |m|
790       [m['ref'].to_i, m['type'].to_sym, m['role']]
791     end
792
793     new_members = new_doc.find("//osm/relation/member").collect do |m|
794       [m['ref'].to_i, m['type'].to_sym, m['role']]
795     end
796
797     doc_members.zip(new_members).each do |d, n|
798       assert_equal d, n, "members are not equal - ordering is wrong? (#{doc}, #{xml})"
799     end
800   end
801
802   ##
803   # create a changeset and yield to the caller to set it up, then assert
804   # that the changeset bounding box is +bbox+.
805   def check_changeset_modify(bbox)
806     ## First test with the private user to check that you get a forbidden
807     basic_authorization(users(:normal_user).email, "test");  
808
809     # create a new changeset for this operation, so we are assured
810     # that the bounding box will be newly-generated.
811     changeset_id = with_controller(ChangesetController.new) do
812       content "<osm><changeset/></osm>"
813       put :create
814       assert_response :forbidden, "shouldn't be able to create changeset for modify test, as should get forbidden"
815     end
816
817     
818     ## Now do the whole thing with the public user
819     basic_authorization(users(:public_user).email, "test")
820     
821     # create a new changeset for this operation, so we are assured
822     # that the bounding box will be newly-generated.
823     changeset_id = with_controller(ChangesetController.new) do
824       content "<osm><changeset/></osm>"
825       put :create
826       assert_response :success, "couldn't create changeset for modify test"
827       @response.body.to_i
828     end
829
830     # go back to the block to do the actual modifies
831     yield changeset_id
832
833     # now download the changeset to check its bounding box
834     with_controller(ChangesetController.new) do
835       get :read, :id => changeset_id
836       assert_response :success, "can't re-read changeset for modify test"
837       assert_select "osm>changeset", 1, "Changeset element doesn't exist in #{@response.body}"
838       assert_select "osm>changeset[id=#{changeset_id}]", 1, "Changeset id=#{changeset_id} doesn't exist in #{@response.body}"
839       assert_select "osm>changeset[min_lon=#{bbox.min_lon}]", 1, "Changeset min_lon wrong in #{@response.body}"
840       assert_select "osm>changeset[min_lat=#{bbox.min_lat}]", 1, "Changeset min_lat wrong in #{@response.body}"
841       assert_select "osm>changeset[max_lon=#{bbox.max_lon}]", 1, "Changeset max_lon wrong in #{@response.body}"
842       assert_select "osm>changeset[max_lat=#{bbox.max_lat}]", 1, "Changeset max_lat wrong in #{@response.body}"
843     end
844   end
845
846   ##
847   # yields the relation with the given +id+ (and optional +version+
848   # to read from the history tables) into the block. the parsed XML
849   # doc is returned.
850   def with_relation(id, ver = nil)
851     if ver.nil?
852       get :read, :id => id
853     else
854       with_controller(OldRelationController.new) do
855         get :version, :id => id, :version => ver
856       end
857     end
858     assert_response :success
859     yield xml_parse(@response.body)
860   end
861
862   ##
863   # updates the relation (XML) +rel+ and 
864   # yields the new version of that relation into the block. 
865   # the parsed XML doc is retured.
866   def with_update(rel)
867     rel_id = rel.find("//osm/relation").first["id"].to_i
868     content rel
869     put :update, :id => rel_id
870     assert_response :success, "can't update relation: #{@response.body}"
871     version = @response.body.to_i
872
873     # now get the new version
874     get :read, :id => rel_id
875     assert_response :success
876     new_rel = xml_parse(@response.body)
877
878     yield new_rel
879
880     return version
881   end
882
883   ##
884   # updates the relation (XML) +rel+ via the diff-upload API and
885   # yields the new version of that relation into the block. 
886   # the parsed XML doc is retured.
887   def with_update_diff(rel)
888     rel_id = rel.find("//osm/relation").first["id"].to_i
889     cs_id = rel.find("//osm/relation").first['changeset'].to_i
890     version = nil
891
892     with_controller(ChangesetController.new) do
893       doc = OSM::API.new.get_xml_doc
894       change = XML::Node.new 'osmChange'
895       doc.root = change
896       modify = XML::Node.new 'modify'
897       change << modify
898       modify << doc.import(rel.find("//osm/relation").first)
899
900       content doc.to_s
901       post :upload, :id => cs_id
902       assert_response :success, "can't upload diff relation: #{@response.body}"
903       version = xml_parse(@response.body).find("//diffResult/relation").first["new_version"].to_i
904     end      
905     
906     # now get the new version
907     get :read, :id => rel_id
908     assert_response :success
909     new_rel = xml_parse(@response.body)
910     
911     yield new_rel
912     
913     return version
914   end
915
916   ##
917   # returns a k->v hash of tags from an xml doc
918   def get_tags_as_hash(a) 
919     a.find("//osm/relation/tag").inject({}) do |h,v|
920       h[v['k']] = v['v']
921       h
922     end
923   end
924   
925   ##
926   # assert that all tags on relation documents +a+ and +b+ 
927   # are equal
928   def assert_tags_equal(a, b)
929     # turn the XML doc into tags hashes
930     a_tags = get_tags_as_hash(a)
931     b_tags = get_tags_as_hash(b)
932
933     assert_equal a_tags.keys, b_tags.keys, "Tag keys should be identical."
934     a_tags.each do |k, v|
935       assert_equal v, b_tags[k], 
936         "Tags which were not altered should be the same. " +
937         "#{a_tags.inspect} != #{b_tags.inspect}"
938     end
939   end
940
941   ##
942   # update the changeset_id of a node element
943   def update_changeset(xml, changeset_id)
944     xml_attr_rewrite(xml, 'changeset', changeset_id)
945   end
946
947   ##
948   # update an attribute in the node element
949   def xml_attr_rewrite(xml, name, value)
950     xml.find("//osm/relation").first[name] = value.to_s
951     return xml
952   end
953
954   ##
955   # parse some xml
956   def xml_parse(xml)
957     parser = XML::Parser.string(xml)
958     parser.parse
959   end
960 end