Merging 16820:16891 from trunk.
[rails.git] / test / functional / node_controller_test.rb
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class NodeControllerTest < ActionController::TestCase
4   api_fixtures
5
6   def test_create
7     # cannot read password from fixture as it is stored as MD5 digest
8     ## First try with no auth
9     
10     # create a node with random lat/lon
11     lat = rand(100)-50 + rand
12     lon = rand(100)-50 + rand
13     # normal user has a changeset open, so we'll use that.
14     changeset = changesets(:normal_user_first_change)
15     # create a minimal xml file
16     content("<osm><node lat='#{lat}' lon='#{lon}' changeset='#{changeset.id}'/></osm>")
17     assert_difference('OldNode.count', 0) do
18       put :create
19     end
20     # hope for unauthorized
21     assert_response :unauthorized, "node upload did not return unauthorized status"
22
23     
24     
25     ## Now try with the user which doesn't have their data public
26     basic_authorization(users(:normal_user).email, "test")
27     
28     # create a node with random lat/lon
29     lat = rand(100)-50 + rand
30     lon = rand(100)-50 + rand
31     # normal user has a changeset open, so we'll use that.
32     changeset = changesets(:normal_user_first_change)
33     # create a minimal xml file
34     content("<osm><node lat='#{lat}' lon='#{lon}' changeset='#{changeset.id}'/></osm>")
35     assert_difference('Node.count', 0) do
36       put :create
37     end
38     # hope for success
39     assert_require_public_data "node create did not return forbidden status"
40
41
42     
43     ## Now try with the user that has the public data
44     basic_authorization(users(:public_user).email, "test")
45     
46     # create a node with random lat/lon
47     lat = rand(100)-50 + rand
48     lon = rand(100)-50 + rand
49     # normal user has a changeset open, so we'll use that.
50     changeset = changesets(:public_user_first_change)
51     # create a minimal xml file
52     content("<osm><node lat='#{lat}' lon='#{lon}' changeset='#{changeset.id}'/></osm>")
53     put :create
54     # hope for success
55     assert_response :success, "node upload did not return success status"
56
57     # read id of created node and search for it
58     nodeid = @response.body
59     checknode = Node.find(nodeid)
60     assert_not_nil checknode, "uploaded node not found in data base after upload"
61     # compare values
62     assert_in_delta lat * 10000000, checknode.latitude, 1, "saved node does not match requested latitude"
63     assert_in_delta lon * 10000000, checknode.longitude, 1, "saved node does not match requested longitude"
64     assert_equal changesets(:public_user_first_change).id, checknode.changeset_id, "saved node does not belong to changeset that it was created in"
65     assert_equal true, checknode.visible, "saved node is not visible"
66   end
67
68   def test_create_invalid_xml
69     ## Only test public user here, as test_create should cover what's the forbiddens
70     ## that would occur here
71     # Initial setup
72     basic_authorization(users(:public_user).email, "test")
73     # normal user has a changeset open, so we'll use that.
74     changeset = changesets(:public_user_first_change)
75     lat = 3.434
76     lon = 3.23
77     
78     # test that the upload is rejected when no lat is supplied
79     # create a minimal xml file
80     content("<osm><node lon='#{lon}' changeset='#{changeset.id}'/></osm>")
81     put :create
82     # hope for success
83     assert_response :bad_request, "node upload did not return bad_request status"
84     assert_equal "Cannot parse valid node from xml string <node lon=\"3.23\" changeset=\"#{changeset.id}\"/>. lat missing", @response.body
85
86     # test that the upload is rejected when no lon is supplied
87     # create a minimal xml file
88     content("<osm><node lat='#{lat}' changeset='#{changeset.id}'/></osm>")
89     put :create
90     # hope for success
91     assert_response :bad_request, "node upload did not return bad_request status"
92     assert_equal "Cannot parse valid node from xml string <node lat=\"3.434\" changeset=\"#{changeset.id}\"/>. lon missing", @response.body
93
94     # test that the upload is rejected when we have a tag which is too long
95     content("<osm><node lat='#{lat}' lon='#{lon}' changeset='#{changeset.id}'><tag k='foo' v='#{'x'*256}'/></node></osm>")
96     put :create
97     assert_response :bad_request, "node upload did not return bad_request status"
98     assert_equal ["NodeTag ", " v: is too long (maximum is 255 characters) (\"#{'x'*256}\")"], @response.body.split(/[0-9]+:/)
99
100   end
101
102   def test_read
103     # check that a visible node is returned properly
104     get :read, :id => current_nodes(:visible_node).id
105     assert_response :success
106
107     # check that an invisible node is not returned
108     get :read, :id => current_nodes(:invisible_node).id
109     assert_response :gone
110
111     # check chat a non-existent node is not returned
112     get :read, :id => 0
113     assert_response :not_found
114   end
115
116   # this tests deletion restrictions - basic deletion is tested in the unit
117   # tests for node!
118   def test_delete
119     ## first try to delete node without auth
120     delete :delete, :id => current_nodes(:visible_node).id
121     assert_response :unauthorized
122     
123     
124     ## now set auth for the non-data public user
125     basic_authorization(users(:normal_user).email, "test");  
126
127     # try to delete with an invalid (closed) changeset
128     content update_changeset(current_nodes(:visible_node).to_xml,
129                              changesets(:normal_user_closed_change).id)
130     delete :delete, :id => current_nodes(:visible_node).id
131     assert_require_public_data("non-public user shouldn't be able to delete node")
132
133     # try to delete with an invalid (non-existent) changeset
134     content update_changeset(current_nodes(:visible_node).to_xml,0)
135     delete :delete, :id => current_nodes(:visible_node).id
136     assert_require_public_data("shouldn't be able to delete node, when user's data is private")
137
138     # valid delete now takes a payload
139     content(nodes(:visible_node).to_xml)
140     delete :delete, :id => current_nodes(:visible_node).id
141     assert_require_public_data("shouldn't be able to delete node when user's data isn't public'")
142
143     # this won't work since the node is already deleted
144     content(nodes(:invisible_node).to_xml)
145     delete :delete, :id => current_nodes(:invisible_node).id
146     assert_require_public_data
147
148     # this won't work since the node never existed
149     delete :delete, :id => 0
150     assert_require_public_data
151
152     ## these test whether nodes which are in-use can be deleted:
153     # in a way...
154     content(nodes(:used_node_1).to_xml)
155     delete :delete, :id => current_nodes(:used_node_1).id
156     assert_require_public_data
157        "shouldn't be able to delete a node used in a way (#{@response.body})"
158
159     # in a relation...
160     content(nodes(:node_used_by_relationship).to_xml)
161     delete :delete, :id => current_nodes(:node_used_by_relationship).id
162     assert_require_public_data
163        "shouldn't be able to delete a node used in a relation (#{@response.body})"
164
165     
166
167     ## now set auth for the public data user
168     basic_authorization(users(:public_user).email, "test");  
169
170     # try to delete with an invalid (closed) changeset
171     content update_changeset(current_nodes(:visible_node).to_xml,
172                              changesets(:normal_user_closed_change).id)
173     delete :delete, :id => current_nodes(:visible_node).id
174     assert_response :conflict
175
176     # try to delete with an invalid (non-existent) changeset
177     content update_changeset(current_nodes(:visible_node).to_xml,0)
178     delete :delete, :id => current_nodes(:visible_node).id
179     assert_response :conflict
180
181     # try to delete a node with a different ID
182     content(nodes(:public_visible_node).to_xml)
183     delete :delete, :id => current_nodes(:visible_node).id
184     assert_response :bad_request, 
185        "should not be able to delete a node with a different ID from the XML"
186
187     # valid delete now takes a payload
188     content(nodes(:public_visible_node).to_xml)
189     delete :delete, :id => current_nodes(:public_visible_node).id
190     assert_response :success
191
192     # valid delete should return the new version number, which should
193     # be greater than the old version number
194     assert @response.body.to_i > current_nodes(:public_visible_node).version,
195        "delete request should return a new version number for node"
196
197     # this won't work since the node is already deleted
198     content(nodes(:invisible_node).to_xml)
199     delete :delete, :id => current_nodes(:invisible_node).id
200     assert_response :gone
201
202     # this won't work since the node never existed
203     delete :delete, :id => 0
204     assert_response :not_found
205
206     ## these test whether nodes which are in-use can be deleted:
207     # in a way...
208     content(nodes(:used_node_1).to_xml)
209     delete :delete, :id => current_nodes(:used_node_1).id
210     assert_response :precondition_failed,
211        "shouldn't be able to delete a node used in a way (#{@response.body})"
212     assert_equal "Precondition failed: Node 3 is still used by way 1.", @response.body
213
214     # in a relation...
215     content(nodes(:node_used_by_relationship).to_xml)
216     delete :delete, :id => current_nodes(:node_used_by_relationship).id
217     assert_response :precondition_failed,
218        "shouldn't be able to delete a node used in a relation (#{@response.body})"
219     assert_match /Precondition failed: Node 5 is still used by relation [13]./, @response.body
220   end
221
222   ##
223   # tests whether the API works and prevents incorrect use while trying
224   # to update nodes.
225   def test_update
226     ## First test with no user credentials
227     # try and update a node without authorisation
228     # first try to delete node without auth
229     content current_nodes(:visible_node).to_xml
230     put :update, :id => current_nodes(:visible_node).id
231     assert_response :unauthorized
232     
233     
234     
235     ## Second test with the private user
236     
237     # setup auth
238     basic_authorization(users(:normal_user).email, "test")
239
240     ## trying to break changesets
241
242     # try and update in someone else's changeset
243     content update_changeset(current_nodes(:visible_node).to_xml,
244                              changesets(:public_user_first_change).id)
245     put :update, :id => current_nodes(:visible_node).id
246     assert_require_public_data "update with other user's changeset should be forbidden when date isn't public"
247
248     # try and update in a closed changeset
249     content update_changeset(current_nodes(:visible_node).to_xml,
250                              changesets(:normal_user_closed_change).id)
251     put :update, :id => current_nodes(:visible_node).id
252     assert_require_public_data "update with closed changeset should be forbidden, when data isn't public"
253
254     # try and update in a non-existant changeset
255     content update_changeset(current_nodes(:visible_node).to_xml, 0)
256     put :update, :id => current_nodes(:visible_node).id
257     assert_require_public_data("update with changeset=0 should be forbidden, when data isn't public")
258
259     ## try and submit invalid updates
260     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lat', 91.0);
261     put :update, :id => current_nodes(:visible_node).id
262     assert_require_public_data "node at lat=91 should be forbidden, when data isn't public"
263
264     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lat', -91.0);
265     put :update, :id => current_nodes(:visible_node).id
266     assert_require_public_data "node at lat=-91 should be forbidden, when data isn't public"
267     
268     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lon', 181.0);
269     put :update, :id => current_nodes(:visible_node).id
270     assert_require_public_data "node at lon=181 should be forbidden, when data isn't public"
271
272     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lon', -181.0);
273     put :update, :id => current_nodes(:visible_node).id
274     assert_require_public_data "node at lon=-181 should be forbidden, when data isn't public"
275     
276     ## finally, produce a good request which should work
277     content current_nodes(:visible_node).to_xml
278     put :update, :id => current_nodes(:visible_node).id
279     assert_require_public_data "should have failed with a forbidden when data isn't public"
280     
281     ## Finally test with the public user
282     
283     # try and update a node without authorisation
284     # first try to delete node without auth
285     content current_nodes(:visible_node).to_xml
286     put :update, :id => current_nodes(:visible_node).id
287     assert_response :forbidden
288     
289     # setup auth
290     basic_authorization(users(:public_user).email, "test")
291
292     ## trying to break changesets
293
294     # try and update in someone else's changeset
295     content update_changeset(current_nodes(:visible_node).to_xml,
296                               changesets(:normal_user_first_change).id)
297     put :update, :id => current_nodes(:visible_node).id
298     assert_response :conflict, "update with other user's changeset should be rejected"
299
300     # try and update in a closed changeset
301     content update_changeset(current_nodes(:visible_node).to_xml,
302                              changesets(:normal_user_closed_change).id)
303     put :update, :id => current_nodes(:visible_node).id
304     assert_response :conflict, "update with closed changeset should be rejected"
305
306     # try and update in a non-existant changeset
307     content update_changeset(current_nodes(:visible_node).to_xml, 0)
308     put :update, :id => current_nodes(:visible_node).id
309     assert_response :conflict, "update with changeset=0 should be rejected"
310
311     ## try and submit invalid updates
312     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lat', 91.0);
313     put :update, :id => current_nodes(:visible_node).id
314     assert_response :bad_request, "node at lat=91 should be rejected"
315
316     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lat', -91.0);
317     put :update, :id => current_nodes(:visible_node).id
318     assert_response :bad_request, "node at lat=-91 should be rejected"
319     
320     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lon', 181.0);
321     put :update, :id => current_nodes(:visible_node).id
322     assert_response :bad_request, "node at lon=181 should be rejected"
323
324     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 'lon', -181.0);
325     put :update, :id => current_nodes(:visible_node).id
326     assert_response :bad_request, "node at lon=-181 should be rejected"
327
328     ## next, attack the versioning
329     current_node_version = current_nodes(:visible_node).version
330
331     # try and submit a version behind
332     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 
333                              'version', current_node_version - 1);
334     put :update, :id => current_nodes(:visible_node).id
335     assert_response :conflict, "should have failed on old version number"
336     
337     # try and submit a version ahead
338     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 
339                              'version', current_node_version + 1);
340     put :update, :id => current_nodes(:visible_node).id
341     assert_response :conflict, "should have failed on skipped version number"
342
343     # try and submit total crap in the version field
344     content xml_attr_rewrite(current_nodes(:visible_node).to_xml, 
345                              'version', 'p1r4t3s!');
346     put :update, :id => current_nodes(:visible_node).id
347     assert_response :conflict, 
348        "should not be able to put 'p1r4at3s!' in the version field"
349     
350     ## try an update with the wrong ID
351     content current_nodes(:public_visible_node).to_xml
352     put :update, :id => current_nodes(:visible_node).id
353     assert_response :bad_request, 
354        "should not be able to update a node with a different ID from the XML"
355
356     ## finally, produce a good request which should work
357     content current_nodes(:public_visible_node).to_xml
358     put :update, :id => current_nodes(:public_visible_node).id
359     assert_response :success, "a valid update request failed"
360   end
361
362   ##
363   # test adding tags to a node
364   def test_duplicate_tags
365     # setup auth
366     basic_authorization(users(:public_user).email, "test")
367
368     # add an identical tag to the node
369     tag_xml = XML::Node.new("tag")
370     tag_xml['k'] = current_node_tags(:public_v_t1).k
371     tag_xml['v'] = current_node_tags(:public_v_t1).v
372
373     # add the tag into the existing xml
374     node_xml = current_nodes(:public_visible_node).to_xml
375     node_xml.find("//osm/node").first << tag_xml
376
377     # try and upload it
378     content node_xml
379     put :update, :id => current_nodes(:public_visible_node).id
380     assert_response :bad_request, 
381       "adding duplicate tags to a node should fail with 'bad request'"
382     assert_equal "Element node/#{current_nodes(:public_visible_node).id} has duplicate tags with key #{current_node_tags(:t1).k}", @response.body
383   end
384
385   # test whether string injection is possible
386   def test_string_injection
387     ## First try with the non-data public user
388     basic_authorization(users(:normal_user).email, "test")
389     changeset_id = changesets(:normal_user_first_change).id
390
391     # try and put something into a string that the API might 
392     # use unquoted and therefore allow code injection...
393     content "<osm><node lat='0' lon='0' changeset='#{changeset_id}'>" +
394       '<tag k="#{@user.inspect}" v="0"/>' +
395       '</node></osm>'
396     put :create
397     assert_require_public_data "Shouldn't be able to create with non-public user"
398     
399     
400     ## Then try with the public data user
401     basic_authorization(users(:public_user).email, "test")
402     changeset_id = changesets(:public_user_first_change).id
403
404     # try and put something into a string that the API might 
405     # use unquoted and therefore allow code injection...
406     content "<osm><node lat='0' lon='0' changeset='#{changeset_id}'>" +
407       '<tag k="#{@user.inspect}" v="0"/>' +
408       '</node></osm>'
409     put :create
410     assert_response :success
411     nodeid = @response.body
412
413     # find the node in the database
414     checknode = Node.find(nodeid)
415     assert_not_nil checknode, "node not found in data base after upload"
416     
417     # and grab it using the api
418     get :read, :id => nodeid
419     assert_response :success
420     apinode = Node.from_xml(@response.body)
421     assert_not_nil apinode, "downloaded node is nil, but shouldn't be"
422     
423     # check the tags are not corrupted
424     assert_equal checknode.tags, apinode.tags
425     assert apinode.tags.include?('#{@user.inspect}')
426   end
427
428   def basic_authorization(user, pass)
429     @request.env["HTTP_AUTHORIZATION"] = "Basic %s" % Base64.encode64("#{user}:#{pass}")
430   end
431
432   def content(c)
433     @request.env["RAW_POST_DATA"] = c.to_s
434   end
435
436   ##
437   # update the changeset_id of a node element
438   def update_changeset(xml, changeset_id)
439     xml_attr_rewrite(xml, 'changeset', changeset_id)
440   end
441
442   ##
443   # update an attribute in the node element
444   def xml_attr_rewrite(xml, name, value)
445     xml.find("//osm/node").first[name] = value.to_s
446     return xml
447   end
448
449   ##
450   # parse some xml
451   def xml_parse(xml)
452     parser = XML::Parser.string(xml)
453     parser.parse
454   end
455 end