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