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