Made full test a bit more thorough.
[rails.git] / test / functional / way_controller_test.rb
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'way_controller'
3
4 class WayControllerTest < ActionController::TestCase
5   api_fixtures
6
7   def basic_authorization(user, pass)
8     @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}")
9   end
10
11   def content(c)
12     @request.env["RAW_POST_DATA"] = c.to_s
13   end
14
15   # -------------------------------------
16   # Test reading ways.
17   # -------------------------------------
18
19   def test_read
20     # check that a visible way is returned properly
21     get :read, :id => current_ways(:visible_way).id
22     assert_response :success
23
24     # check that an invisible way is not returned
25     get :read, :id => current_ways(:invisible_way).id
26     assert_response :gone
27
28     # check chat a non-existent way is not returned
29     get :read, :id => 0
30     assert_response :not_found
31   end
32
33   ##
34   # check the "full" mode
35   def test_full
36     Way.find(:all).each do |way|
37       get :full, :id => way.id
38
39       # full call should say "gone" for non-visible ways...
40       unless way.visible
41         assert_response :gone
42         next
43       end
44
45       # otherwise it should say success
46       assert_response :success
47       
48       # Check the way is correctly returned
49       assert_select "osm way[id=#{way.id}][version=#{way.version}][visible=#{way.visible}]", 1
50       
51       # check that each node in the way appears once in the output as a 
52       # reference and as the node element. note the slightly dodgy assumption
53       # that nodes appear only once. this is currently the case with the
54       # fixtures, but it doesn't have to be.
55       way.nodes.each do |n|
56         assert_select "osm way nd[ref=#{n.id}]", 1
57         assert_select "osm node[id=#{n.id}][version=#{n.version}][lat=#{n.lat}][lon=#{n.lon}]", 1
58       end
59     end
60   end
61
62   # -------------------------------------
63   # Test simple way creation.
64   # -------------------------------------
65
66   def test_create
67     nid1 = current_nodes(:used_node_1).id
68     nid2 = current_nodes(:used_node_2).id
69     basic_authorization "test@openstreetmap.org", "test"
70
71     # use the first user's open changeset
72     changeset_id = changesets(:normal_user_first_change).id
73     
74     # create a way with pre-existing nodes
75     content "<osm><way changeset='#{changeset_id}'>" +
76       "<nd ref='#{nid1}'/><nd ref='#{nid2}'/>" + 
77       "<tag k='test' v='yes' /></way></osm>"
78     put :create
79     # hope for success
80     assert_response :success, 
81         "way upload did not return success status"
82     # read id of created way and search for it
83     wayid = @response.body
84     checkway = Way.find(wayid)
85     assert_not_nil checkway, 
86         "uploaded way not found in data base after upload"
87     # compare values
88     assert_equal checkway.nds.length, 2, 
89         "saved way does not contain exactly one node"
90     assert_equal checkway.nds[0], nid1, 
91         "saved way does not contain the right node on pos 0"
92     assert_equal checkway.nds[1], nid2, 
93         "saved way does not contain the right node on pos 1"
94     assert_equal checkway.changeset_id, changeset_id,
95         "saved way does not belong to the correct changeset"
96     assert_equal users(:normal_user).id, checkway.changeset.user_id, 
97         "saved way does not belong to user that created it"
98     assert_equal true, checkway.visible, 
99         "saved way is not visible"
100   end
101
102   # -------------------------------------
103   # Test creating some invalid ways.
104   # -------------------------------------
105
106   def test_create_invalid
107     basic_authorization "test@openstreetmap.org", "test"
108
109     # use the first user's open changeset
110     open_changeset_id = changesets(:normal_user_first_change).id
111     closed_changeset_id = changesets(:normal_user_closed_change).id
112     nid1 = current_nodes(:used_node_1).id
113
114     # create a way with non-existing node
115     content "<osm><way changeset='#{open_changeset_id}'>" + 
116       "<nd ref='0'/><tag k='test' v='yes' /></way></osm>"
117     put :create
118     # expect failure
119     assert_response :precondition_failed, 
120         "way upload with invalid node did not return 'precondition failed'"
121
122     # create a way with no nodes
123     content "<osm><way changeset='#{open_changeset_id}'>" +
124       "<tag k='test' v='yes' /></way></osm>"
125     put :create
126     # expect failure
127     assert_response :precondition_failed, 
128         "way upload with no node did not return 'precondition failed'"
129
130     # create a way inside a closed changeset
131     content "<osm><way changeset='#{closed_changeset_id}'>" +
132       "<nd ref='#{nid1}'/></way></osm>"
133     put :create
134     # expect failure
135     assert_response :conflict, 
136         "way upload to closed changeset did not return 'conflict'"    
137   end
138
139   # -------------------------------------
140   # Test deleting ways.
141   # -------------------------------------
142   
143   def test_delete
144     # first try to delete way without auth
145     delete :delete, :id => current_ways(:visible_way).id
146     assert_response :unauthorized
147
148     # now set auth
149     basic_authorization("test@openstreetmap.org", "test");  
150
151     # this shouldn't work as with the 0.6 api we need pay load to delete
152     delete :delete, :id => current_ways(:visible_way).id
153     assert_response :bad_request
154     
155     # Now try without having a changeset
156     content "<osm><way id='#{current_ways(:visible_way).id}'></osm>"
157     delete :delete, :id => current_ways(:visible_way).id
158     assert_response :bad_request
159     
160     # try to delete with an invalid (closed) changeset
161     content update_changeset(current_ways(:visible_way).to_xml,
162                              changesets(:normal_user_closed_change).id)
163     delete :delete, :id => current_ways(:visible_way).id
164     assert_response :conflict
165
166     # try to delete with an invalid (non-existent) changeset
167     content update_changeset(current_ways(:visible_way).to_xml,0)
168     delete :delete, :id => current_ways(:visible_way).id
169     assert_response :conflict
170
171     # Now try with a valid changeset
172     content current_ways(:visible_way).to_xml
173     delete :delete, :id => current_ways(:visible_way).id
174     assert_response :success
175
176     # check the returned value - should be the new version number
177     # valid delete should return the new version number, which should
178     # be greater than the old version number
179     assert @response.body.to_i > current_ways(:visible_way).version,
180        "delete request should return a new version number for way"
181
182     # this won't work since the way is already deleted
183     content current_ways(:invisible_way).to_xml
184     delete :delete, :id => current_ways(:invisible_way).id
185     assert_response :gone
186
187     # this shouldn't work as the way is used in a relation
188     content current_ways(:used_way).to_xml
189     delete :delete, :id => current_ways(:used_way).id
190     assert_response :precondition_failed, 
191        "shouldn't be able to delete a way used in a relation (#{@response.body})"
192
193     # this won't work since the way never existed
194     delete :delete, :id => 0
195     assert_response :not_found
196   end
197
198   # ------------------------------------------------------------
199   # test tags handling
200   # ------------------------------------------------------------
201
202   ##
203   # Try adding a duplicate of an existing tag to a way
204   def test_add_duplicate_tags
205     # setup auth
206     basic_authorization(users(:normal_user).email, "test")
207
208     # add an identical tag to the way
209     tag_xml = XML::Node.new("tag")
210     tag_xml['k'] = current_way_tags(:t1).k
211     tag_xml['v'] = current_way_tags(:t1).v
212
213     # add the tag into the existing xml
214     way_xml = current_ways(:visible_way).to_xml
215     way_xml.find("//osm/way").first << tag_xml
216
217     # try and upload it
218     content way_xml
219     put :update, :id => current_ways(:visible_way).id
220     assert_response :bad_request, 
221        "adding a duplicate tag to a way should fail with 'bad request'"
222   end
223
224   ##
225   # Try adding a new duplicate tags to a way
226   def test_new_duplicate_tags
227     # setup auth
228     basic_authorization(users(:normal_user).email, "test")
229
230     # create duplicate tag
231     tag_xml = XML::Node.new("tag")
232     tag_xml['k'] = "i_am_a_duplicate"
233     tag_xml['v'] = "foobar"
234
235     # add the tag into the existing xml
236     way_xml = current_ways(:visible_way).to_xml
237
238     # add two copies of the tag
239     way_xml.find("//osm/way").first << tag_xml.copy(true) << tag_xml
240
241     # try and upload it
242     content way_xml
243     put :update, :id => current_ways(:visible_way).id
244     assert_response :bad_request, 
245        "adding new duplicate tags to a way should fail with 'bad request'"
246   end
247
248   ##
249   # Try adding a new duplicate tags to a way.
250   # But be a bit subtle - use unicode decoding ambiguities to use different
251   # binary strings which have the same decoding.
252   #
253   # NOTE: I'm not sure this test is working correctly, as a lot of the tag
254   # keys seem to come out as "addr��housenumber". It might be something to
255   # do with Ruby's unicode handling...?
256   def test_invalid_duplicate_tags
257     # setup auth
258     basic_authorization(users(:normal_user).email, "test")
259
260     # add the tag into the existing xml
261     way_str = "<osm><way changeset='1'>"
262     way_str << "<tag k='addr:housenumber' v='1'/>"
263
264     # all of these keys have the same unicode decoding, but are binary
265     # not equal. libxml should make these identical as it decodes the
266     # XML document...
267     [ "addr\xc0\xbahousenumber",
268       "addr\xe0\x80\xbahousenumber",
269       "addr\xf0\x80\x80\xbahousenumber" ].each do |key|
270       # copy the XML doc to add the tags
271       way_str_copy = way_str.clone
272
273       # add all new tags to the way
274       way_str_copy << "<tag k='" << key << "' v='1'/>"
275       way_str_copy << "</way></osm>";
276
277       # try and upload it
278       content way_str_copy
279       put :create
280       assert_response :bad_request, 
281          "adding new duplicate tags to a way should fail with 'bad request'"
282     end
283   end
284
285   ##
286   # test that a call to ways_for_node returns all ways that contain the node
287   # and none that don't.
288   def test_ways_for_node
289     # in current fixtures ways 1 and 3 all use node 3. ways 2 and 4 
290     # *used* to use it but doesn't.
291     get :ways_for_node, :id => current_nodes(:used_node_1).id
292     assert_response :success
293     ways_xml = XML::Parser.string(@response.body).parse
294     assert_not_nil ways_xml, "failed to parse ways_for_node response"
295
296     # check that the set of IDs match expectations
297     expected_way_ids = [ current_ways(:visible_way).id,
298                          current_ways(:used_way).id
299                        ]
300     found_way_ids = ways_xml.find("//osm/way").collect { |w| w["id"].to_i }
301     assert_equal expected_way_ids, found_way_ids,
302       "expected ways for node #{current_nodes(:used_node_1).id} did not match found"
303     
304     # check the full ways to ensure we're not missing anything
305     expected_way_ids.each do |id|
306       way_xml = ways_xml.find("//osm/way[@id=#{id}]").first
307       assert_ways_are_equal(Way.find(id),
308                             Way.from_xml_node(way_xml))
309     end
310   end
311
312   ##
313   # update the changeset_id of a node element
314   def update_changeset(xml, changeset_id)
315     xml_attr_rewrite(xml, 'changeset', changeset_id)
316   end
317
318   ##
319   # update an attribute in the node element
320   def xml_attr_rewrite(xml, name, value)
321     xml.find("//osm/way").first[name] = value.to_s
322     return xml
323   end
324 end