Some fixes to the rails part of the AMF controller and associated test.
[rails.git] / test / functional / amf_controller_test.rb
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'stringio'
3 include Potlatch
4
5 class AmfControllerTest < ActionController::TestCase
6   api_fixtures
7
8   # this should be what AMF controller returns when the bbox of a request
9   # is invalid or too large.
10   BOUNDARY_ERROR = [-2,"Sorry - I can't get the map for that area."]
11
12   def test_getway
13     # check a visible way
14     id = current_ways(:visible_way).id
15     amf_content "getway", "/1", [id]
16     post :amf_read
17     assert_response :success
18     amf_parse_response                                         
19     assert_equal amf_result("/1")[0], id
20   end
21
22   def test_getway_invisible
23     # check an invisible way
24     id = current_ways(:invisible_way).id
25     amf_content "getway", "/1", [id]
26     post :amf_read
27     assert_response :success
28     amf_parse_response
29     way = amf_result("/1")
30     assert_equal way[0], id
31     assert way[1].empty? and way[2].empty?
32   end
33
34   def test_getway_nonexistent
35     # check chat a non-existent way is not returned
36     amf_content "getway", "/1", [0]
37     post :amf_read
38     assert_response :success
39     amf_parse_response
40     way = amf_result("/1")
41     assert_equal way[0], 0
42     assert way[1].empty? and way[2].empty?
43   end
44
45   def test_whichways
46     node = current_nodes(:used_node_1)
47     minlon = node.lon-0.1
48     minlat = node.lat-0.1
49     maxlon = node.lon+0.1
50     maxlat = node.lat+0.1
51     amf_content "whichways", "/1", [minlon, minlat, maxlon, maxlat]
52     post :amf_read
53     assert_response :success
54     amf_parse_response 
55
56     # check contents of message
57     map = amf_result "/1"
58     assert_equal 0, map[0], 'first map element should be 0'
59     assert_equal Array, map[1].class, 'second map element should be an array'
60     # TODO: looks like amf_controller changed since this test was written
61     # so someone who knows what they're doing should check this!
62     ways = map[1].collect { |x| x[0] }
63     assert ways.include?(current_ways(:used_way).id),
64       "map should include used way"
65     assert !ways.include?(current_ways(:invisible_way).id),
66       'map should not include deleted way'
67   end
68
69   ##
70   # checks that too-large a bounding box will not be served.
71   def test_whichways_toobig
72     bbox = [-0.1,-0.1,1.1,1.1]
73     check_bboxes_are_bad [bbox] do |map,bbox|
74       assert_equal BOUNDARY_ERROR, map, "AMF controller should have returned an error."
75     end
76   end
77
78   ##
79   # checks that an invalid bounding box will not be served. in this case
80   # one with max < min latitudes.
81   #
82   # NOTE: the controller expands the bbox by 0.01 in each direction!
83   def test_whichways_badlat
84     bboxes = [[0,0.1,0.1,0], [-0.1,80,0.1,70], [0.24,54.35,0.25,54.33]]
85     check_bboxes_are_bad bboxes do |map, bbox|
86       assert_equal BOUNDARY_ERROR, map, "AMF controller should have returned an error #{bbox.inspect}."
87     end
88   end
89
90   ##
91   # same as test_whichways_badlat, but for longitudes
92   #
93   # NOTE: the controller expands the bbox by 0.01 in each direction!
94   def test_whichways_badlon
95     bboxes = [[80,-0.1,70,0.1], [54.35,0.24,54.33,0.25]]
96     check_bboxes_are_bad bboxes do |map, bbox|
97       assert_equal BOUNDARY_ERROR, map, "AMF controller should have returned an error #{bbox.inspect}."
98     end
99   end
100
101   def test_whichways_deleted
102     node = current_nodes(:used_node_1)
103     minlon = node.lon-0.1
104     minlat = node.lat-0.1
105     maxlon = node.lon+0.1
106     maxlat = node.lat+0.1
107     amf_content "whichways_deleted", "/1", [minlon, minlat, maxlon, maxlat]
108     post :amf_read
109     assert_response :success
110     amf_parse_response
111
112     # check contents of message
113     map = amf_result "/1"
114     assert_equal 0, map[0], 'first map element should be 0'
115     assert_equal Array, map[1].class, 'second map element should be an array'
116     # TODO: looks like amf_controller changed since this test was written
117     # so someone who knows what they're doing should check this!
118     assert !map[1].include?(current_ways(:used_way).id),
119       "map should not include used way"
120     assert map[1].include?(current_ways(:invisible_way).id),
121       'map should include deleted way'
122   end
123
124   def test_whichways_deleted_toobig
125     bbox = [-0.1,-0.1,1.1,1.1]
126     amf_content "whichways_deleted", "/1", bbox
127     post :amf_read
128     assert_response :success
129     amf_parse_response 
130
131     map = amf_result "/1"
132     assert_equal BOUNDARY_ERROR, map, "AMF controller should have returned an error."
133   end
134
135   def test_getrelation
136     id = current_relations(:visible_relation).id
137     amf_content "getrelation", "/1", [id]
138     post :amf_read
139     assert_response :success
140     amf_parse_response
141     assert_equal amf_result("/1")[0], id
142   end
143
144   def test_getrelation_invisible
145     id = current_relations(:invisible_relation).id
146     amf_content "getrelation", "/1", [id]
147     post :amf_read
148     assert_response :success
149     amf_parse_response
150     rel = amf_result("/1")
151     assert_equal rel[0], id
152     assert rel[1].empty? and rel[2].empty?
153   end
154
155   def test_getrelation_nonexistent
156     id = 0
157     amf_content "getrelation", "/1", [id]
158     post :amf_read
159     assert_response :success
160     amf_parse_response
161     rel = amf_result("/1")
162     assert_equal rel[0], id
163     assert rel[1].empty? and rel[2].empty?
164   end
165
166   def test_getway_old
167     # try to get the last visible version (specified by <0) (should be current version)
168     latest = current_ways(:way_with_versions)
169     # try to get version 1
170     v1 = ways(:way_with_versions_v1)
171     {latest => -1, v1 => v1.version}.each do |way, v|
172       amf_content "getway_old", "/1", [way.id, v]
173       post :amf_read
174       assert_response :success
175       amf_parse_response
176       returned_way = amf_result("/1")
177       assert_equal returned_way[1], way.id
178       assert_equal returned_way[4], way.version
179     end
180   end
181
182   def test_getway_old_nonexistent
183     # try to get the last version+10 (shoudn't exist)
184     latest = current_ways(:way_with_versions)
185     # try to get last visible version of non-existent way
186     # try to get specific version of non-existent way
187     {nil => -1, nil => 1, latest => latest.version + 10}.each do |way, v|
188       amf_content "getway_old", "/1", [way.nil? ? 0 : way.id, v]
189       post :amf_read
190       assert_response :success
191       amf_parse_response
192       returned_way = amf_result("/1")
193       assert returned_way[2].empty?
194       assert returned_way[3].empty?
195       assert returned_way[4] < 0
196     end
197   end
198
199   def test_getway_history
200     latest = current_ways(:way_with_versions)
201     amf_content "getway_history", "/1", [latest.id]
202     post :amf_read
203     assert_response :success
204     amf_parse_response
205     history = amf_result("/1")
206
207     # ['way',wayid,history]
208     assert_equal history[0], 'way'
209     assert_equal history[1], latest.id
210     assert_equal history[2].first[0], latest.version
211     assert_equal history[2].last[0], ways(:way_with_versions_v1).version
212   end
213
214   def test_getway_history_nonexistent
215     amf_content "getway_history", "/1", [0]
216     post :amf_read
217     assert_response :success
218     amf_parse_response
219     history = amf_result("/1")
220
221     # ['way',wayid,history]
222     assert_equal history[0], 'way'
223     assert_equal history[1], 0
224     assert history[2].empty?
225   end
226
227   def test_getnode_history
228     latest = current_nodes(:node_with_versions)
229     amf_content "getnode_history", "/1", [latest.id]
230     post :amf_read
231     assert_response :success
232     amf_parse_response
233     history = amf_result("/1")
234
235     # ['node',nodeid,history]
236     assert_equal history[0], 'node', 
237       'first element should be "node"'
238     assert_equal history[1], latest.id,
239       'second element should be the input node ID'
240     # NOTE: changed this test to match what amf_controller actually 
241     # outputs - which may or may not be what potlatch is expecting.
242     # someone who knows potlatch (i.e: richard f) should review this.
243     assert_equal history[2].first[0], latest.version,
244       'first part of third element should be the latest version'
245     assert_equal history[2].last[0], 
246       nodes(:node_with_versions_v1).version,
247       'second part of third element should be the initial version'
248   end
249
250   def test_getnode_history_nonexistent
251     amf_content "getnode_history", "/1", [0]
252     post :amf_read
253     assert_response :success
254     amf_parse_response
255     history = amf_result("/1")
256
257     # ['node',nodeid,history]
258     assert_equal history[0], 'node'
259     assert_equal history[1], 0
260     assert history[2].empty?
261   end
262
263   # ************************************************************
264   # AMF Write tests
265   def test_putpoi_update_valid
266     nd = current_nodes(:visible_node)
267     amf_content "putpoi", "/1", ["test@openstreetmap.org:test", nd.changeset_id, nd.version, nd.id, nd.lon, nd.lat, nd.tags, nd.visible]
268     post :amf_write
269     assert_response :success
270     amf_parse_response
271     result = amf_result("/1")
272     
273     assert_equal 0, result[0]
274     assert_equal nd.id, result[1]
275     assert_equal nd.id, result[2]
276     assert_equal nd.version+1, result[3]
277     
278     # Now try to update again, with a different lat/lon, using the updated version number
279     lat = nd.lat+0.1
280     lon = nd.lon-0.1
281     amf_content "putpoi", "/2", ["test@openstreetmap.org:test", nd.changeset_id, nd.version+1, nd.id, lon, lat, nd.tags, nd.visible]
282     post :amf_write
283     assert_response :success
284     amf_parse_response
285     result = amf_result("/2")
286     
287     assert_equal 0, result[0]
288     assert_equal nd.id, result[1]
289     assert_equal nd.id, result[2]
290     assert_equal nd.version+2, result[3]
291   end
292   
293   # Check that we can create a no valid poi
294   # Using similar method for the node controller test
295   def test_putpoi_create_valid
296     # This node has no tags
297     nd = Node.new
298     # create a node with random lat/lon
299     lat = rand(100)-50 + rand
300     lon = rand(100)-50 + rand
301     # normal user has a changeset open
302     changeset = changesets(:normal_user_first_change)
303     
304     amf_content "putpoi", "/1", ["test@openstreetmap.org:test", changeset.id, nil, nil, lon, lat, {}, nil]
305     post :amf_write
306     assert_response :success
307     amf_parse_response
308     result = amf_result("/1")
309     
310     # check the array returned by the amf
311     assert_equal 4, result.size
312     assert_equal 0, result[0], "expected to get the status ok from the amf"
313     assert_equal 0, result[1], "The old id should be 0"
314     assert result[2] > 0, "The new id should be greater than 0"
315     assert_equal 1, result[3], "The new version should be 1"
316     
317     # Finally check that the node that was saved has saved the data correctly 
318     # in both the current and history tables
319     # First check the current table
320     current_node = Node.find(result[2])
321     assert_in_delta lat, current_node.lat, 0.00001, "The latitude was not retreieved correctly"
322     assert_in_delta lon, current_node.lon, 0.00001, "The longitude was not retreived correctly"
323     assert_equal 0, current_node.tags.size, "There seems to be a tag that has been added to the node"
324     assert_equal result[3], current_node.version, "The version returned, is different to the one returned by the amf"
325     # Now check the history table
326     historic_nodes = Node.find(:all, :conditions => { :id => result[2] })
327     assert_equal 1, historic_nodes.size, "There should only be one historic node created"
328     first_historic_node = historic_nodes.first
329     assert_in_delta lat, first_historic_node.lat, 0.00001, "The latitude was not retreived correctly"
330     assert_in_delta lon, first_historic_node.lon, 0.00001, "The longitude was not retreuved correctly"
331     assert_equal 0, first_historic_node.tags.size, "There seems to be a tag that have been attached to this node"
332     assert_equal result[3], first_historic_node.version, "The version returned, is different to the one returned by the amf"
333     
334     ####
335     # This node has some tags
336     tnd = Node.new
337     # create a node with random lat/lon
338     lat = rand(100)-50 + rand
339     lon = rand(100)-50 + rand
340     # normal user has a changeset open
341     changeset = changesets(:normal_user_first_change)
342     
343     amf_content "putpoi", "/2", ["test@openstreetmap.org:test", changeset.id, nil, nil, lon, lat, { "key" => "value", "ping" => "pong" }, nil]
344     post :amf_write
345     assert_response :success
346     amf_parse_response
347     result = amf_result("/2")
348
349     # check the array returned by the amf
350     assert_equal 4, result.size
351     assert_equal 0, result[0], "Expected to get the status ok in the amf"
352     assert_equal 0, result[1], "The old id should be 0"
353     assert result[2] > 0, "The new id should be greater than 0"
354     assert_equal 1, result[3], "The new version should be 1"
355     
356     # Finally check that the node that was saved has saved the data correctly 
357     # in both the current and history tables
358     # First check the current table
359     current_node = Node.find(result[2])
360     assert_in_delta lat, current_node.lat, 0.00001, "The latitude was not retreieved correctly"
361     assert_in_delta lon, current_node.lon, 0.00001, "The longitude was not retreived correctly"
362     assert_equal 2, current_node.tags.size, "There seems to be a tag that has been added to the node"
363     assert_equal({ "key" => "value", "ping" => "pong" }, current_node.tags, "tags are different")
364     assert_equal result[3], current_node.version, "The version returned, is different to the one returned by the amf"
365     # Now check the history table
366     historic_nodes = Node.find(:all, :conditions => { :id => result[2] })
367     assert_equal 1, historic_nodes.size, "There should only be one historic node created"
368     first_historic_node = historic_nodes.first
369     assert_in_delta lat, first_historic_node.lat, 0.00001, "The latitude was not retreived correctly"
370     assert_in_delta lon, first_historic_node.lon, 0.00001, "The longitude was not retreuved correctly"
371     assert_equal 2, first_historic_node.tags.size, "There seems to be a tag that have been attached to this node"
372     assert_equal({ "key" => "value", "ping" => "pong" }, first_historic_node.tags, "tags are different")
373     assert_equal result[3], first_historic_node.version, "The version returned, is different to the one returned by the amf"
374
375   end
376
377   # ************************************************************
378   # AMF Helper functions
379
380   # Get the result record for the specified ID
381   # It's an assertion FAIL if the record does not exist
382   def amf_result ref
383     assert @amf_result.has_key?("#{ref}/onResult")
384     @amf_result["#{ref}/onResult"]
385   end
386
387   # Encode the AMF message to invoke "target" with parameters as
388   # the passed data. The ref is used to retrieve the results.
389   def amf_content(target, ref, data)
390     a,b=1.divmod(256)
391     c = StringIO.new()
392     c.write 0.chr+0.chr   # version 0
393     c.write 0.chr+0.chr   # n headers
394     c.write a.chr+b.chr   # n bodies
395     c.write AMF.encodestring(target)
396     c.write AMF.encodestring(ref)
397     c.write [-1].pack("N")
398     c.write AMF.encodevalue(data)
399
400     @request.env["RAW_POST_DATA"] = c.string
401   end
402
403   # Parses the @response object as an AMF messsage.
404   # The result is a hash of message_ref => data.
405   # The attribute @amf_result is initialised to this hash.
406   def amf_parse_response
407     if @response.body.class.to_s == 'Proc'
408       res = StringIO.new()
409       @response.body.call @response, res
410       req = StringIO.new(res.string)
411     else
412       req = StringIO.new(@response.body)
413     end
414     req.read(2)   # version
415
416     # parse through any headers
417         headers=AMF.getint(req)                                 # Read number of headers
418         headers.times do                                                # Read each header
419           name=AMF.getstring(req)                               #  |
420           req.getc                                                              #  | skip boolean
421           value=AMF.getvalue(req)                               #  |
422         end
423
424     # parse through responses
425     results = {}
426     bodies=AMF.getint(req)                                      # Read number of bodies
427         bodies.times do                                                 # Read each body
428           message=AMF.getstring(req)                    #  | get message name
429           index=AMF.getstring(req)                              #  | get index in response sequence
430           bytes=AMF.getlong(req)                                #  | get total size in bytes
431           args=AMF.getvalue(req)                                #  | get response (probably an array)
432       results[message] = args
433     end
434     @amf_result = results
435     results
436   end
437
438   ##
439   # given an array of bounding boxes (each an array of 4 floats), call the
440   # AMF "whichways" controller for each and pass the result back to the
441   # caller's block for assertion testing.
442   def check_bboxes_are_bad(bboxes)
443     bboxes.each do |bbox|
444       amf_content "whichways", "/1", bbox
445       post :amf_read
446       assert_response :success
447       amf_parse_response
448
449       # pass the response back to the caller's block to be tested
450       # against what the caller expected.
451       map = amf_result "/1"
452       yield map, bbox
453     end
454   end
455 end