1613fbf7a8cb924b8241d0c3ee67f065a38af94b
[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 => "read", :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 => "relations" }
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 reading relations.
48   # -------------------------------------
49
50   def test_read
51     # check that a visible relation is returned properly
52     get :read, :params => { :id => create(:relation).id }
53     assert_response :success
54
55     # check that an invisible relation is not returned
56     get :read, :params => { :id => create(:relation, :deleted).id }
57     assert_response :gone
58
59     # check chat a non-existent relation is not returned
60     get :read, :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_relations
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 :relations
172     assert_response :bad_request
173
174     # check error when no parameter value provided
175     get :relations, :params => { :relations => "" }
176     assert_response :bad_request
177
178     # test a working call
179     get :relations, :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 :relations, :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     content "<osm><relation changeset='#{private_changeset.id}'><tag k='test' v='yes' /></relation></osm>"
210     put :create
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     content "<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
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     content "<osm><relation changeset='#{private_changeset.id}'>" \
230             "<member  ref='#{node.id}' type='node'/>" + "<tag k='test' v='yes' /></relation></osm>"
231     put :create
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     content "<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
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     content "<osm><relation changeset='#{changeset.id}'><tag k='test' v='yes' /></relation></osm>"
252     put :create
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 :read, :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     content "<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
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 :read, :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     content "<osm><relation changeset='#{changeset.id}'>" \
311             "<member  ref='#{node.id}' type='node'/>" + "<tag k='test' v='yes' /></relation></osm>"
312     put :create
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 :read, :params => { :id => relationid }
335     assert_response :success
336
337     ###
338     # create an relation with a way and a node as members
339     content "<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
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 :read, :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       content rel
447       put :update, :params => { :id => other_relation.id }
448       assert_response :bad_request
449     end
450   end
451
452   # -------------------------------------
453   # Test creating some invalid relations.
454   # -------------------------------------
455
456   def test_create_invalid
457     user = create(:user)
458     changeset = create(:changeset, :user => user)
459
460     basic_authorization user.email, "test"
461
462     # create a relation with non-existing node as member
463     content "<osm><relation changeset='#{changeset.id}'>" \
464             "<member type='node' ref='0'/><tag k='test' v='yes' />" \
465             "</relation></osm>"
466     put :create
467     # expect failure
468     assert_response :precondition_failed,
469                     "relation upload with invalid node did not return 'precondition failed'"
470     assert_equal "Precondition failed: Relation with id  cannot be saved due to Node with id 0", @response.body
471   end
472
473   # -------------------------------------
474   # Test creating a relation, with some invalid XML
475   # -------------------------------------
476   def test_create_invalid_xml
477     user = create(:user)
478     changeset = create(:changeset, :user => user)
479     node = create(:node)
480
481     basic_authorization user.email, "test"
482
483     # create some xml that should return an error
484     content "<osm><relation changeset='#{changeset.id}'>" \
485             "<member type='type' ref='#{node.id}' role=''/>" \
486             "<tag k='tester' v='yep'/></relation></osm>"
487     put :create
488     # expect failure
489     assert_response :bad_request
490     assert_match(/Cannot parse valid relation from xml string/, @response.body)
491     assert_match(/The type is not allowed only, /, @response.body)
492   end
493
494   # -------------------------------------
495   # Test deleting relations.
496   # -------------------------------------
497
498   def test_delete
499     private_user = create(:user, :data_public => false)
500     private_user_closed_changeset = create(:changeset, :closed, :user => private_user)
501     user = create(:user)
502     closed_changeset = create(:changeset, :closed, :user => user)
503     changeset = create(:changeset, :user => user)
504     relation = create(:relation)
505     used_relation = create(:relation)
506     super_relation = create(:relation_member, :member => used_relation).relation
507     deleted_relation = create(:relation, :deleted)
508     multi_tag_relation = create(:relation)
509     create_list(:relation_tag, 4, :relation => multi_tag_relation)
510
511     ## First try to delete relation without auth
512     delete :delete, :params => { :id => relation.id }
513     assert_response :unauthorized
514
515     ## Then try with the private user, to make sure that you get a forbidden
516     basic_authorization private_user.email, "test"
517
518     # this shouldn't work, as we should need the payload...
519     delete :delete, :params => { :id => relation.id }
520     assert_response :forbidden
521
522     # try to delete without specifying a changeset
523     content "<osm><relation id='#{relation.id}'/></osm>"
524     delete :delete, :params => { :id => relation.id }
525     assert_response :forbidden
526
527     # try to delete with an invalid (closed) changeset
528     content update_changeset(relation.to_xml,
529                              private_user_closed_changeset.id)
530     delete :delete, :params => { :id => relation.id }
531     assert_response :forbidden
532
533     # try to delete with an invalid (non-existent) changeset
534     content update_changeset(relation.to_xml, 0)
535     delete :delete, :params => { :id => relation.id }
536     assert_response :forbidden
537
538     # this won't work because the relation is in-use by another relation
539     content(used_relation.to_xml)
540     delete :delete, :params => { :id => used_relation.id }
541     assert_response :forbidden
542
543     # this should work when we provide the appropriate payload...
544     content(relation.to_xml)
545     delete :delete, :params => { :id => relation.id }
546     assert_response :forbidden
547
548     # this won't work since the relation is already deleted
549     content(deleted_relation.to_xml)
550     delete :delete, :params => { :id => deleted_relation.id }
551     assert_response :forbidden
552
553     # this won't work since the relation never existed
554     delete :delete, :params => { :id => 0 }
555     assert_response :forbidden
556
557     ## now set auth for the public user
558     basic_authorization user.email, "test"
559
560     # this shouldn't work, as we should need the payload...
561     delete :delete, :params => { :id => relation.id }
562     assert_response :bad_request
563
564     # try to delete without specifying a changeset
565     content "<osm><relation id='#{relation.id}' version='#{relation.version}' /></osm>"
566     delete :delete, :params => { :id => relation.id }
567     assert_response :bad_request
568     assert_match(/Changeset id is missing/, @response.body)
569
570     # try to delete with an invalid (closed) changeset
571     content update_changeset(relation.to_xml,
572                              closed_changeset.id)
573     delete :delete, :params => { :id => relation.id }
574     assert_response :conflict
575
576     # try to delete with an invalid (non-existent) changeset
577     content update_changeset(relation.to_xml, 0)
578     delete :delete, :params => { :id => relation.id }
579     assert_response :conflict
580
581     # this won't work because the relation is in a changeset owned by someone else
582     content update_changeset(relation.to_xml, create(:changeset).id)
583     delete :delete, :params => { :id => relation.id }
584     assert_response :conflict,
585                     "shouldn't be able to delete a relation in a changeset owned by someone else (#{@response.body})"
586
587     # this won't work because the relation in the payload is different to that passed
588     content update_changeset(relation.to_xml, changeset.id)
589     delete :delete, :params => { :id => create(:relation).id }
590     assert_response :bad_request, "shouldn't be able to delete a relation when payload is different to the url"
591
592     # this won't work because the relation is in-use by another relation
593     content update_changeset(used_relation.to_xml, changeset.id)
594     delete :delete, :params => { :id => used_relation.id }
595     assert_response :precondition_failed,
596                     "shouldn't be able to delete a relation used in a relation (#{@response.body})"
597     assert_equal "Precondition failed: The relation #{used_relation.id} is used in relation #{super_relation.id}.", @response.body
598
599     # this should work when we provide the appropriate payload...
600     content update_changeset(multi_tag_relation.to_xml, changeset.id)
601     delete :delete, :params => { :id => multi_tag_relation.id }
602     assert_response :success
603
604     # valid delete should return the new version number, which should
605     # be greater than the old version number
606     assert @response.body.to_i > multi_tag_relation.version,
607            "delete request should return a new version number for relation"
608
609     # this won't work since the relation is already deleted
610     content update_changeset(deleted_relation.to_xml, changeset.id)
611     delete :delete, :params => { :id => deleted_relation.id }
612     assert_response :gone
613
614     # Public visible relation needs to be deleted
615     content update_changeset(super_relation.to_xml, changeset.id)
616     delete :delete, :params => { :id => super_relation.id }
617     assert_response :success
618
619     # this works now because the relation which was using this one
620     # has been deleted.
621     content update_changeset(used_relation.to_xml, changeset.id)
622     delete :delete, :params => { :id => used_relation.id }
623     assert_response :success,
624                     "should be able to delete a relation used in an old relation (#{@response.body})"
625
626     # this won't work since the relation never existed
627     delete :delete, :params => { :id => 0 }
628     assert_response :not_found
629   end
630
631   ##
632   # when a relation's tag is modified then it should put the bounding
633   # box of all its members into the changeset.
634   def test_tag_modify_bounding_box
635     relation = create(:relation)
636     node1 = create(:node, :lat => 3, :lon => 3)
637     node2 = create(:node, :lat => 5, :lon => 5)
638     way = create(:way)
639     create(:way_node, :way => way, :node => node1)
640     create(:relation_member, :relation => relation, :member => way)
641     create(:relation_member, :relation => relation, :member => node2)
642     # the relation contains nodes1 and node2 (node1
643     # indirectly via the way), so the bbox should be [3,3,5,5].
644     check_changeset_modify(BoundingBox.new(3, 3, 5, 5)) do |changeset_id|
645       # add a tag to an existing relation
646       relation_xml = relation.to_xml
647       relation_element = relation_xml.find("//osm/relation").first
648       new_tag = XML::Node.new("tag")
649       new_tag["k"] = "some_new_tag"
650       new_tag["v"] = "some_new_value"
651       relation_element << new_tag
652
653       # update changeset ID to point to new changeset
654       update_changeset(relation_xml, changeset_id)
655
656       # upload the change
657       content relation_xml
658       put :update, :params => { :id => relation.id }
659       assert_response :success, "can't update relation for tag/bbox test"
660     end
661   end
662
663   ##
664   # add a member to a relation and check the bounding box is only that
665   # element.
666   def test_add_member_bounding_box
667     relation = create(:relation)
668     node1 = create(:node, :lat => 4, :lon => 4)
669     node2 = create(:node, :lat => 7, :lon => 7)
670     way1 = create(:way)
671     create(:way_node, :way => way1, :node => create(:node, :lat => 8, :lon => 8))
672     way2 = create(:way)
673     create(:way_node, :way => way2, :node => create(:node, :lat => 9, :lon => 9), :sequence_id => 1)
674     create(:way_node, :way => way2, :node => create(:node, :lat => 10, :lon => 10), :sequence_id => 2)
675
676     [node1, node2, way1, way2].each do |element|
677       bbox = element.bbox.to_unscaled
678       check_changeset_modify(bbox) do |changeset_id|
679         relation_xml = Relation.find(relation.id).to_xml
680         relation_element = relation_xml.find("//osm/relation").first
681         new_member = XML::Node.new("member")
682         new_member["ref"] = element.id.to_s
683         new_member["type"] = element.class.to_s.downcase
684         new_member["role"] = "some_role"
685         relation_element << new_member
686
687         # update changeset ID to point to new changeset
688         update_changeset(relation_xml, changeset_id)
689
690         # upload the change
691         content relation_xml
692         put :update, :params => { :id => relation.id }
693         assert_response :success, "can't update relation for add #{element.class}/bbox test: #{@response.body}"
694
695         # get it back and check the ordering
696         get :read, :params => { :id => relation.id }
697         assert_response :success, "can't read back the relation: #{@response.body}"
698         check_ordering(relation_xml, @response.body)
699       end
700     end
701   end
702
703   ##
704   # remove a member from a relation and check the bounding box is
705   # only that element.
706   def test_remove_member_bounding_box
707     relation = create(:relation)
708     node1 = create(:node, :lat => 3, :lon => 3)
709     node2 = create(:node, :lat => 5, :lon => 5)
710     create(:relation_member, :relation => relation, :member => node1)
711     create(:relation_member, :relation => relation, :member => node2)
712
713     check_changeset_modify(BoundingBox.new(5, 5, 5, 5)) do |changeset_id|
714       # remove node 5 (5,5) from an existing relation
715       relation_xml = relation.to_xml
716       relation_xml
717         .find("//osm/relation/member[@type='node'][@ref='#{node2.id}']")
718         .first.remove!
719
720       # update changeset ID to point to new changeset
721       update_changeset(relation_xml, changeset_id)
722
723       # upload the change
724       content relation_xml
725       put :update, :params => { :id => relation.id }
726       assert_response :success, "can't update relation for remove node/bbox test"
727     end
728   end
729
730   ##
731   # check that relations are ordered
732   def test_relation_member_ordering
733     user = create(:user)
734     changeset = create(:changeset, :user => user)
735     node1 = create(:node)
736     node2 = create(:node)
737     node3 = create(:node)
738     way1 = create(:way_with_nodes, :nodes_count => 2)
739     way2 = create(:way_with_nodes, :nodes_count => 2)
740
741     basic_authorization user.email, "test"
742
743     doc_str = <<OSM.strip_heredoc
744       <osm>
745        <relation changeset='#{changeset.id}'>
746         <member ref='#{node1.id}' type='node' role='first'/>
747         <member ref='#{node2.id}' type='node' role='second'/>
748         <member ref='#{way1.id}' type='way' role='third'/>
749         <member ref='#{way2.id}' type='way' role='fourth'/>
750        </relation>
751       </osm>
752 OSM
753     doc = XML::Parser.string(doc_str).parse
754
755     content doc
756     put :create
757     assert_response :success, "can't create a relation: #{@response.body}"
758     relation_id = @response.body.to_i
759
760     # get it back and check the ordering
761     get :read, :params => { :id => relation_id }
762     assert_response :success, "can't read back the relation: #{@response.body}"
763     check_ordering(doc, @response.body)
764
765     # insert a member at the front
766     new_member = XML::Node.new "member"
767     new_member["ref"] = node3.id.to_s
768     new_member["type"] = "node"
769     new_member["role"] = "new first"
770     doc.find("//osm/relation").first.child.prev = new_member
771     # update the version, should be 1?
772     doc.find("//osm/relation").first["id"] = relation_id.to_s
773     doc.find("//osm/relation").first["version"] = 1.to_s
774
775     # upload the next version of the relation
776     content doc
777     put :update, :params => { :id => relation_id }
778     assert_response :success, "can't update relation: #{@response.body}"
779     assert_equal 2, @response.body.to_i
780
781     # get it back again and check the ordering again
782     get :read, :params => { :id => relation_id }
783     assert_response :success, "can't read back the relation: #{@response.body}"
784     check_ordering(doc, @response.body)
785
786     # check the ordering in the history tables:
787     with_controller(OldRelationsController.new) do
788       get :version, :params => { :id => relation_id, :version => 2 }
789       assert_response :success, "can't read back version 2 of the relation #{relation_id}"
790       check_ordering(doc, @response.body)
791     end
792   end
793
794   ##
795   # check that relations can contain duplicate members
796   def test_relation_member_duplicates
797     private_user = create(:user, :data_public => false)
798     user = create(:user)
799     changeset = create(:changeset, :user => user)
800     node1 = create(:node)
801     node2 = create(:node)
802
803     doc_str = <<OSM.strip_heredoc
804       <osm>
805        <relation changeset='#{changeset.id}'>
806         <member ref='#{node1.id}' type='node' role='forward'/>
807         <member ref='#{node2.id}' type='node' role='forward'/>
808         <member ref='#{node1.id}' type='node' role='forward'/>
809         <member ref='#{node2.id}' type='node' role='forward'/>
810        </relation>
811       </osm>
812 OSM
813     doc = XML::Parser.string(doc_str).parse
814
815     ## First try with the private user
816     basic_authorization private_user.email, "test"
817
818     content doc
819     put :create
820     assert_response :forbidden
821
822     ## Now try with the public user
823     basic_authorization user.email, "test"
824
825     content doc
826     put :create
827     assert_response :success, "can't create a relation: #{@response.body}"
828     relation_id = @response.body.to_i
829
830     # get it back and check the ordering
831     get :read, :params => { :id => relation_id }
832     assert_response :success, "can't read back the relation: #{relation_id}"
833     check_ordering(doc, @response.body)
834   end
835
836   ##
837   # test that the ordering of elements in the history is the same as in current.
838   def test_history_ordering
839     user = create(:user)
840     changeset = create(:changeset, :user => user)
841     node1 = create(:node)
842     node2 = create(:node)
843     node3 = create(:node)
844     node4 = create(:node)
845
846     doc_str = <<OSM.strip_heredoc
847       <osm>
848        <relation changeset='#{changeset.id}'>
849         <member ref='#{node1.id}' type='node' role='forward'/>
850         <member ref='#{node4.id}' type='node' role='forward'/>
851         <member ref='#{node3.id}' type='node' role='forward'/>
852         <member ref='#{node2.id}' type='node' role='forward'/>
853        </relation>
854       </osm>
855 OSM
856     doc = XML::Parser.string(doc_str).parse
857     basic_authorization user.email, "test"
858
859     content doc
860     put :create
861     assert_response :success, "can't create a relation: #{@response.body}"
862     relation_id = @response.body.to_i
863
864     # check the ordering in the current tables:
865     get :read, :params => { :id => relation_id }
866     assert_response :success, "can't read back the relation: #{@response.body}"
867     check_ordering(doc, @response.body)
868
869     # check the ordering in the history tables:
870     with_controller(OldRelationsController.new) do
871       get :version, :params => { :id => relation_id, :version => 1 }
872       assert_response :success, "can't read back version 1 of the relation: #{@response.body}"
873       check_ordering(doc, @response.body)
874     end
875   end
876
877   ##
878   # remove all the members from a relation. the result is pretty useless, but
879   # still technically valid.
880   def test_remove_all_members
881     relation = create(:relation)
882     node1 = create(:node, :lat => 3, :lon => 3)
883     node2 = create(:node, :lat => 5, :lon => 5)
884     way = create(:way)
885     create(:way_node, :way => way, :node => node1)
886     create(:relation_member, :relation => relation, :member => way)
887     create(:relation_member, :relation => relation, :member => node2)
888
889     check_changeset_modify(BoundingBox.new(3, 3, 5, 5)) do |changeset_id|
890       relation_xml = relation.to_xml
891       relation_xml
892         .find("//osm/relation/member")
893         .each(&:remove!)
894
895       # update changeset ID to point to new changeset
896       update_changeset(relation_xml, changeset_id)
897
898       # upload the change
899       content relation_xml
900       put :update, :params => { :id => relation.id }
901       assert_response :success, "can't update relation for remove all members test"
902       checkrelation = Relation.find(relation.id)
903       assert_not_nil(checkrelation,
904                      "uploaded relation not found in database after upload")
905       assert_equal(0, checkrelation.members.length,
906                    "relation contains members but they should have all been deleted")
907     end
908   end
909
910   # ============================================================
911   # utility functions
912   # ============================================================
913
914   ##
915   # checks that the XML document and the string arguments have
916   # members in the same order.
917   def check_ordering(doc, xml)
918     new_doc = XML::Parser.string(xml).parse
919
920     doc_members = doc.find("//osm/relation/member").collect do |m|
921       [m["ref"].to_i, m["type"].to_sym, m["role"]]
922     end
923
924     new_members = new_doc.find("//osm/relation/member").collect do |m|
925       [m["ref"].to_i, m["type"].to_sym, m["role"]]
926     end
927
928     doc_members.zip(new_members).each do |d, n|
929       assert_equal d, n, "members are not equal - ordering is wrong? (#{doc}, #{xml})"
930     end
931   end
932
933   ##
934   # create a changeset and yield to the caller to set it up, then assert
935   # that the changeset bounding box is +bbox+.
936   def check_changeset_modify(bbox)
937     ## First test with the private user to check that you get a forbidden
938     basic_authorization create(:user, :data_public => false).email, "test"
939
940     # create a new changeset for this operation, so we are assured
941     # that the bounding box will be newly-generated.
942     changeset_id = with_controller(ChangesetController.new) do
943       content "<osm><changeset/></osm>"
944       put :create
945       assert_response :forbidden, "shouldn't be able to create changeset for modify test, as should get forbidden"
946     end
947
948     ## Now do the whole thing with the public user
949     basic_authorization create(:user).email, "test"
950
951     # create a new changeset for this operation, so we are assured
952     # that the bounding box will be newly-generated.
953     changeset_id = with_controller(ChangesetController.new) do
954       content "<osm><changeset/></osm>"
955       put :create
956       assert_response :success, "couldn't create changeset for modify test"
957       @response.body.to_i
958     end
959
960     # go back to the block to do the actual modifies
961     yield changeset_id
962
963     # now download the changeset to check its bounding box
964     with_controller(ChangesetController.new) do
965       get :read, :params => { :id => changeset_id }
966       assert_response :success, "can't re-read changeset for modify test"
967       assert_select "osm>changeset", 1, "Changeset element doesn't exist in #{@response.body}"
968       assert_select "osm>changeset[id='#{changeset_id}']", 1, "Changeset id=#{changeset_id} doesn't exist in #{@response.body}"
969       assert_select "osm>changeset[min_lon='#{format('%.7f', bbox.min_lon)}']", 1, "Changeset min_lon wrong in #{@response.body}"
970       assert_select "osm>changeset[min_lat='#{format('%.7f', bbox.min_lat)}']", 1, "Changeset min_lat wrong in #{@response.body}"
971       assert_select "osm>changeset[max_lon='#{format('%.7f', bbox.max_lon)}']", 1, "Changeset max_lon wrong in #{@response.body}"
972       assert_select "osm>changeset[max_lat='#{format('%.7f', bbox.max_lat)}']", 1, "Changeset max_lat wrong in #{@response.body}"
973     end
974   end
975
976   ##
977   # yields the relation with the given +id+ (and optional +version+
978   # to read from the history tables) into the block. the parsed XML
979   # doc is returned.
980   def with_relation(id, ver = nil)
981     if ver.nil?
982       get :read, :params => { :id => id }
983     else
984       with_controller(OldRelationsController.new) do
985         get :version, :params => { :id => id, :version => ver }
986       end
987     end
988     assert_response :success
989     yield xml_parse(@response.body)
990   end
991
992   ##
993   # updates the relation (XML) +rel+ and
994   # yields the new version of that relation into the block.
995   # the parsed XML doc is retured.
996   def with_update(rel)
997     rel_id = rel.find("//osm/relation").first["id"].to_i
998     content rel
999     put :update, :params => { :id => rel_id }
1000     assert_response :success, "can't update relation: #{@response.body}"
1001     version = @response.body.to_i
1002
1003     # now get the new version
1004     get :read, :params => { :id => rel_id }
1005     assert_response :success
1006     new_rel = xml_parse(@response.body)
1007
1008     yield new_rel
1009
1010     version
1011   end
1012
1013   ##
1014   # updates the relation (XML) +rel+ via the diff-upload API and
1015   # yields the new version of that relation into the block.
1016   # the parsed XML doc is retured.
1017   def with_update_diff(rel)
1018     rel_id = rel.find("//osm/relation").first["id"].to_i
1019     cs_id = rel.find("//osm/relation").first["changeset"].to_i
1020     version = nil
1021
1022     with_controller(ChangesetController.new) do
1023       doc = OSM::API.new.get_xml_doc
1024       change = XML::Node.new "osmChange"
1025       doc.root = change
1026       modify = XML::Node.new "modify"
1027       change << modify
1028       modify << doc.import(rel.find("//osm/relation").first)
1029
1030       content doc.to_s
1031       post :upload, :params => { :id => cs_id }
1032       assert_response :success, "can't upload diff relation: #{@response.body}"
1033       version = xml_parse(@response.body).find("//diffResult/relation").first["new_version"].to_i
1034     end
1035
1036     # now get the new version
1037     get :read, :params => { :id => rel_id }
1038     assert_response :success
1039     new_rel = xml_parse(@response.body)
1040
1041     yield new_rel
1042
1043     version
1044   end
1045
1046   ##
1047   # returns a k->v hash of tags from an xml doc
1048   def get_tags_as_hash(a)
1049     a.find("//osm/relation/tag").sort_by { |v| v["k"] }.each_with_object({}) do |v, h|
1050       h[v["k"]] = v["v"]
1051     end
1052   end
1053
1054   ##
1055   # assert that all tags on relation documents +a+ and +b+
1056   # are equal
1057   def assert_tags_equal(a, b)
1058     # turn the XML doc into tags hashes
1059     a_tags = get_tags_as_hash(a)
1060     b_tags = get_tags_as_hash(b)
1061
1062     assert_equal a_tags.keys, b_tags.keys, "Tag keys should be identical."
1063     a_tags.each do |k, v|
1064       assert_equal v, b_tags[k],
1065                    "Tags which were not altered should be the same. " \
1066                    "#{a_tags.inspect} != #{b_tags.inspect}"
1067     end
1068   end
1069
1070   ##
1071   # update the changeset_id of a node element
1072   def update_changeset(xml, changeset_id)
1073     xml_attr_rewrite(xml, "changeset", changeset_id)
1074   end
1075
1076   ##
1077   # update an attribute in the node element
1078   def xml_attr_rewrite(xml, name, value)
1079     xml.find("//osm/relation").first[name] = value.to_s
1080     xml
1081   end
1082
1083   ##
1084   # parse some xml
1085   def xml_parse(xml)
1086     parser = XML::Parser.string(xml)
1087     parser.parse
1088   end
1089 end