Added a query method for querying bounding boxes, times and users of changesets....
[rails.git] / test / functional / changeset_controller_test.rb
1 require File.dirname(__FILE__) + '/../test_helper'
2 require 'changeset_controller'
3
4 class ChangesetControllerTest < 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 simple changeset creation
17   # -----------------------
18   
19   def test_create
20     basic_authorization "test@openstreetmap.org", "test"
21     
22     # Create the first user's changeset
23     content "<osm><changeset>" +
24       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
25       "</changeset></osm>"
26     put :create
27     
28     assert_response :success, "Creation of changeset did not return sucess status"
29     newid = @response.body
30   end
31   
32   def test_create_invalid
33     basic_authorization "test@openstreetmap.org", "test"
34     content "<osm><changeset></osm>"
35     put :create
36     assert_response :bad_request, "creating a invalid changeset should fail"
37   end
38
39   ##
40   # check that the changeset can be read and returns the correct
41   # document structure.
42   def test_read
43     changeset_id = changesets(:normal_user_first_change).id
44     get :read, :id => changeset_id
45     assert_response :success, "cannot get first changeset"
46     
47     assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
48     assert_select "osm>changeset[id=#{changeset_id}]", 1
49   end
50   
51   def test_close
52     # FIXME FIXME FIXME!
53   end
54
55   ##
56   # upload something simple, but valid and check that it can 
57   # be read back ok.
58   def test_upload_simple_valid
59     basic_authorization "test@openstreetmap.org", "test"
60
61     # simple diff to change a node, way and relation by removing 
62     # their tags
63     diff = <<EOF
64 <osmChange>
65  <modify>
66   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
67   <way id='1' changeset='1' version='1'>
68    <nd ref='3'/>
69   </way>
70  </modify>
71  <modify>
72   <relation id='1' changeset='1' version='1'>
73    <member type='way' role='some' ref='3'/>
74    <member type='node' role='some' ref='5'/>
75    <member type='relation' role='some' ref='3'/>
76   </relation>
77  </modify>
78 </osmChange>
79 EOF
80
81     # upload it
82     content diff
83     post :upload, :id => 1
84     assert_response :success, 
85       "can't upload a simple valid diff to changeset: #{@response.body}"
86
87     # check that the changes made it into the database
88     assert_equal 0, Node.find(1).tags.size, "node 1 should now have no tags"
89     assert_equal 0, Way.find(1).tags.size, "way 1 should now have no tags"
90     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
91   end
92     
93   ##
94   # upload something which creates new objects using placeholders
95   def test_upload_create_valid
96     basic_authorization "test@openstreetmap.org", "test"
97
98     # simple diff to create a node way and relation using placeholders
99     diff = <<EOF
100 <osmChange>
101  <create>
102   <node id='-1' lon='0' lat='0' changeset='1'>
103    <tag k='foo' v='bar'/>
104    <tag k='baz' v='bat'/>
105   </node>
106   <way id='-1' changeset='1'>
107    <nd ref='3'/>
108   </way>
109  </create>
110  <create>
111   <relation id='-1' changeset='1'>
112    <member type='way' role='some' ref='3'/>
113    <member type='node' role='some' ref='5'/>
114    <member type='relation' role='some' ref='3'/>
115   </relation>
116  </create>
117 </osmChange>
118 EOF
119
120     # upload it
121     content diff
122     post :upload, :id => 1
123     assert_response :success, 
124       "can't upload a simple valid creation to changeset: #{@response.body}"
125
126     # check the returned payload
127     assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
128     assert_select "osm>node", 1
129     assert_select "osm>way", 1
130     assert_select "osm>relation", 1
131
132     # inspect the response to find out what the new element IDs are
133     doc = XML::Parser.string(@response.body).parse
134     new_node_id = doc.find("//osm/node").first["new_id"].to_i
135     new_way_id = doc.find("//osm/way").first["new_id"].to_i
136     new_rel_id = doc.find("//osm/relation").first["new_id"].to_i
137
138     # check the old IDs are all present and negative one
139     assert_equal -1, doc.find("//osm/node").first["old_id"].to_i
140     assert_equal -1, doc.find("//osm/way").first["old_id"].to_i
141     assert_equal -1, doc.find("//osm/relation").first["old_id"].to_i
142
143     # check the versions are present and equal one
144     assert_equal 1, doc.find("//osm/node").first["new_version"].to_i
145     assert_equal 1, doc.find("//osm/way").first["new_version"].to_i
146     assert_equal 1, doc.find("//osm/relation").first["new_version"].to_i
147
148     # check that the changes made it into the database
149     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
150     assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
151     assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
152   end
153     
154   ##
155   # test a complex delete where we delete elements which rely on eachother
156   # in the same transaction.
157   def test_upload_delete
158     basic_authorization "test@openstreetmap.org", "test"
159
160     diff = XML::Document.new
161     diff.root = XML::Node.new "osmChange"
162     delete = XML::Node.new "delete"
163     diff.root << delete
164     delete << current_relations(:visible_relation).to_xml_node
165     delete << current_relations(:used_relation).to_xml_node
166     delete << current_ways(:used_way).to_xml_node
167     delete << current_nodes(:node_used_by_relationship).to_xml_node
168
169     # upload it
170     content diff
171     post :upload, :id => 1
172     assert_response :success, 
173       "can't upload a deletion diff to changeset: #{@response.body}"
174
175     # check that everything was deleted
176     assert_equal false, Node.find(current_nodes(:node_used_by_relationship).id).visible
177     assert_equal false, Way.find(current_ways(:used_way).id).visible
178     assert_equal false, Relation.find(current_relations(:visible_relation).id).visible
179     assert_equal false, Relation.find(current_relations(:used_relation).id).visible
180   end
181
182   ##
183   # test that deleting stuff in a transaction doesn't bypass the checks
184   # to ensure that used elements are not deleted.
185   def test_upload_delete_invalid
186     basic_authorization "test@openstreetmap.org", "test"
187
188     diff = XML::Document.new
189     diff.root = XML::Node.new "osmChange"
190     delete = XML::Node.new "delete"
191     diff.root << delete
192     delete << current_relations(:visible_relation).to_xml_node
193     delete << current_ways(:used_way).to_xml_node
194     delete << current_nodes(:node_used_by_relationship).to_xml_node
195
196     # upload it
197     content diff
198     post :upload, :id => 1
199     assert_response :precondition_failed, 
200       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
201
202     # check that nothing was, in fact, deleted
203     assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
204     assert_equal true, Way.find(current_ways(:used_way).id).visible
205     assert_equal true, Relation.find(current_relations(:visible_relation).id).visible
206   end
207
208   ##
209   # upload something which creates new objects and inserts them into
210   # existing containers using placeholders.
211   def test_upload_complex
212     basic_authorization "test@openstreetmap.org", "test"
213
214     # simple diff to create a node way and relation using placeholders
215     diff = <<EOF
216 <osmChange>
217  <create>
218   <node id='-1' lon='0' lat='0' changeset='1'>
219    <tag k='foo' v='bar'/>
220    <tag k='baz' v='bat'/>
221   </node>
222  </create>
223  <modify>
224   <way id='1' changeset='1' version='1'>
225    <nd ref='-1'/>
226    <nd ref='3'/>
227   </way>
228   <relation id='1' changeset='1' version='1'>
229    <member type='way' role='some' ref='3'/>
230    <member type='node' role='some' ref='-1'/>
231    <member type='relation' role='some' ref='3'/>
232   </relation>
233  </modify>
234 </osmChange>
235 EOF
236
237     # upload it
238     content diff
239     post :upload, :id => 1
240     assert_response :success, 
241       "can't upload a complex diff to changeset: #{@response.body}"
242
243     # check the returned payload
244     assert_select "osm[version=#{API_VERSION}][generator=\"#{GENERATOR}\"]", 1
245     assert_select "osm>node", 1
246     assert_select "osm>way", 1
247     assert_select "osm>relation", 1
248
249     # inspect the response to find out what the new element IDs are
250     doc = XML::Parser.string(@response.body).parse
251     new_node_id = doc.find("//osm/node").first["new_id"].to_i
252
253     # check that the changes made it into the database
254     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
255     assert_equal [new_node_id, 3], Way.find(1).nds, "way nodes should match"
256     Relation.find(1).members.each do |type,id,role|
257       if type == 'node'
258         assert_equal new_node_id, id, "relation should contain new node"
259       end
260     end
261   end
262     
263   ##
264   # create a diff which references several changesets, which should cause
265   # a rollback and none of the diff gets committed
266   def test_upload_invalid_changesets
267     basic_authorization "test@openstreetmap.org", "test"
268
269     # simple diff to create a node way and relation using placeholders
270     diff = <<EOF
271 <osmChange>
272  <modify>
273   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
274   <way id='1' changeset='1' version='1'>
275    <nd ref='3'/>
276   </way>
277  </modify>
278  <modify>
279   <relation id='1' changeset='1' version='1'>
280    <member type='way' role='some' ref='3'/>
281    <member type='node' role='some' ref='5'/>
282    <member type='relation' role='some' ref='3'/>
283   </relation>
284  </modify>
285  <create>
286   <node id='-1' changeset='4'>
287    <tag k='foo' v='bar'/>
288    <tag k='baz' v='bat'/>
289   </node>
290  </create>
291 </osmChange>
292 EOF
293     # cache the objects before uploading them
294     node = current_nodes(:visible_node)
295     way = current_ways(:visible_way)
296     rel = current_relations(:visible_relation)
297
298     # upload it
299     content diff
300     post :upload, :id => 1
301     assert_response :conflict, 
302       "uploading a diff with multiple changsets should have failed"
303
304     # check that objects are unmodified
305     assert_nodes_are_equal(node, Node.find(1))
306     assert_ways_are_equal(way, Way.find(1))
307   end
308     
309   ##
310   # upload multiple versions of the same element in the same diff.
311   def test_upload_multiple_valid
312     basic_authorization "test@openstreetmap.org", "test"
313
314     # change the location of a node multiple times, each time referencing
315     # the last version. doesn't this depend on version numbers being
316     # sequential?
317     diff = <<EOF
318 <osmChange>
319  <modify>
320   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
321   <node id='1' lon='1' lat='0' changeset='1' version='2'/>
322   <node id='1' lon='1' lat='1' changeset='1' version='3'/>
323   <node id='1' lon='1' lat='2' changeset='1' version='4'/>
324   <node id='1' lon='2' lat='2' changeset='1' version='5'/>
325   <node id='1' lon='3' lat='2' changeset='1' version='6'/>
326   <node id='1' lon='3' lat='3' changeset='1' version='7'/>
327   <node id='1' lon='9' lat='9' changeset='1' version='8'/>
328  </modify>
329 </osmChange>
330 EOF
331
332     # upload it
333     content diff
334     post :upload, :id => 1
335     assert_response :success, 
336       "can't upload multiple versions of an element in a diff: #{@response.body}"
337   end
338
339   ##
340   # upload multiple versions of the same element in the same diff, but
341   # keep the version numbers the same.
342   def test_upload_multiple_duplicate
343     basic_authorization "test@openstreetmap.org", "test"
344
345     diff = <<EOF
346 <osmChange>
347  <modify>
348   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
349   <node id='1' lon='1' lat='1' changeset='1' version='1'/>
350  </modify>
351 </osmChange>
352 EOF
353
354     # upload it
355     content diff
356     post :upload, :id => 1
357     assert_response :conflict, 
358       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
359   end
360
361   ##
362   # try to upload some elements without specifying the version
363   def test_upload_missing_version
364     basic_authorization "test@openstreetmap.org", "test"
365
366     diff = <<EOF
367 <osmChange>
368  <modify>
369   <node id='1' lon='1' lat='1' changeset='1'/>
370  </modify>
371 </osmChange>
372 EOF
373
374     # upload it
375     content diff
376     post :upload, :id => 1
377     assert_response :bad_request, 
378       "shouldn't be able to upload an element without version: #{@response.body}"
379   end
380   
381   ##
382   # try to upload with commands other than create, modify, or delete
383   def test_action_upload_invalid
384     basic_authorization "test@openstreetmap.org", "test"
385     
386     diff = <<EOF
387 <osmChange>
388   <ping>
389     <node id='1' lon='1' lat='1' changeset='1' />
390   </ping>
391 </osmChange>
392 EOF
393   content diff
394   post :upload, :id => 1
395   assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
396   assert_equal @response.body, "Unknown action ping, choices are create, modify, delete."
397   end
398
399   ##
400   # when we make some simple changes we get the same changes back from the 
401   # diff download.
402   def test_diff_download_simple
403     basic_authorization(users(:normal_user).email, "test")
404
405     # create a temporary changeset
406     content "<osm><changeset>" +
407       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
408       "</changeset></osm>"
409     put :create
410     assert_response :success
411     changeset_id = @response.body.to_i
412
413     # add a diff to it
414     diff = <<EOF
415 <osmChange>
416  <modify>
417   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
418   <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
419   <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
420   <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
421   <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
422   <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
423   <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
424   <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
425  </modify>
426 </osmChange>
427 EOF
428
429     # upload it
430     content diff
431     post :upload, :id => changeset_id
432     assert_response :success, 
433       "can't upload multiple versions of an element in a diff: #{@response.body}"
434     
435     get :download, :id => changeset_id
436     assert_response :success
437
438     assert_select "osmChange", 1
439     assert_select "osmChange>modify", 8
440     assert_select "osmChange>modify>node", 8
441   end
442   
443   ##
444   # when we make some complex changes we get the same changes back from the 
445   # diff download.
446   def test_diff_download_complex
447     basic_authorization(users(:normal_user).email, "test")
448
449     # create a temporary changeset
450     content "<osm><changeset>" +
451       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
452       "</changeset></osm>"
453     put :create
454     assert_response :success
455     changeset_id = @response.body.to_i
456
457     # add a diff to it
458     diff = <<EOF
459 <osmChange>
460  <delete>
461   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
462  </delete>
463  <create>
464   <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
465   <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
466   <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
467  </create>
468  <modify>
469   <node id='3' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
470   <way id='1' changeset='#{changeset_id}' version='1'>
471    <nd ref='3'/>
472    <nd ref='-1'/>
473    <nd ref='-2'/>
474    <nd ref='-3'/>
475   </way>
476  </modify>
477 </osmChange>
478 EOF
479
480     # upload it
481     content diff
482     post :upload, :id => changeset_id
483     assert_response :success, 
484       "can't upload multiple versions of an element in a diff: #{@response.body}"
485     
486     get :download, :id => changeset_id
487     assert_response :success
488
489     assert_select "osmChange", 1
490     assert_select "osmChange>create", 3
491     assert_select "osmChange>delete", 1
492     assert_select "osmChange>modify", 2
493     assert_select "osmChange>create>node", 3
494     assert_select "osmChange>delete>node", 1 
495     assert_select "osmChange>modify>node", 1
496     assert_select "osmChange>modify>way", 1
497   end
498
499   ##
500   # check that the bounding box of a changeset gets updated correctly
501   def test_changeset_bbox
502     basic_authorization "test@openstreetmap.org", "test"
503
504     # create a new changeset
505     content "<osm><changeset/></osm>"
506     put :create
507     assert_response :success, "Creating of changeset failed."
508     changeset_id = @response.body.to_i
509     
510     # add a single node to it
511     with_controller(NodeController.new) do
512       content "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
513       put :create
514       assert_response :success, "Couldn't create node."
515     end
516
517     # get the bounding box back from the changeset
518     get :read, :id => changeset_id
519     assert_response :success, "Couldn't read back changeset."
520     assert_select "osm>changeset[min_lon=1]", 1
521     assert_select "osm>changeset[max_lon=1]", 1
522     assert_select "osm>changeset[min_lat=2]", 1
523     assert_select "osm>changeset[max_lat=2]", 1
524
525     # add another node to it
526     with_controller(NodeController.new) do
527       content "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
528       put :create
529       assert_response :success, "Couldn't create second node."
530     end
531
532     # get the bounding box back from the changeset
533     get :read, :id => changeset_id
534     assert_response :success, "Couldn't read back changeset for the second time."
535     assert_select "osm>changeset[min_lon=1]", 1
536     assert_select "osm>changeset[max_lon=2]", 1
537     assert_select "osm>changeset[min_lat=1]", 1
538     assert_select "osm>changeset[max_lat=2]", 1
539
540     # add (delete) a way to it
541     with_controller(WayController.new) do
542       content update_changeset(current_ways(:visible_way).to_xml,
543                                changeset_id)
544       put :delete, :id => current_ways(:visible_way).id
545       assert_response :success, "Couldn't delete a way."
546     end
547
548     # get the bounding box back from the changeset
549     get :read, :id => changeset_id
550     assert_response :success, "Couldn't read back changeset for the third time."
551     assert_select "osm>changeset[min_lon=1]", 1
552     assert_select "osm>changeset[max_lon=3]", 1
553     assert_select "osm>changeset[min_lat=1]", 1
554     assert_select "osm>changeset[max_lat=3]", 1    
555   end
556
557   ##
558   # test that the changeset :include method works as it should
559   def test_changeset_include
560     basic_authorization "test@openstreetmap.org", "test"
561
562     # create a new changeset
563     content "<osm><changeset/></osm>"
564     put :create
565     assert_response :success, "Creating of changeset failed."
566     changeset_id = @response.body.to_i
567
568     # NOTE: the include method doesn't over-expand, like inserting
569     # a real method does. this is because we expect the client to 
570     # know what it is doing!
571     check_after_include(changeset_id,  1,  1, [ 1,  1,  1,  1])
572     check_after_include(changeset_id,  3,  3, [ 1,  1,  3,  3])
573     check_after_include(changeset_id,  4,  2, [ 1,  1,  4,  3])
574     check_after_include(changeset_id,  2,  2, [ 1,  1,  4,  3])
575     check_after_include(changeset_id, -1, -1, [-1, -1,  4,  3])
576     check_after_include(changeset_id, -2,  5, [-2, -1,  4,  5])
577   end
578
579   ##
580   # check searching for changesets by bbox
581   def test_changeset_by_bbox
582     get :query, :bbox => "-10,-10, 10, 10"
583     assert_response :success, "can't get changesets in bbox"
584     # FIXME: write the actual test bit after fixing the fixtures!
585   end
586
587   #------------------------------------------------------------
588   # utility functions
589   #------------------------------------------------------------
590
591   ##
592   # call the include method and assert properties of the bbox
593   def check_after_include(changeset_id, lon, lat, bbox)
594     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
595     post :include, :id => changeset_id
596     assert_response :success, "Setting include of changeset failed: #{@response.body}"
597
598     # check exactly one changeset
599     assert_select "osm>changeset", 1
600     assert_select "osm>changeset[id=#{changeset_id}]", 1
601
602     # check the bbox
603     doc = XML::Parser.string(@response.body).parse
604     changeset = doc.find("//osm/changeset").first
605     assert_equal bbox[0], changeset['min_lon'].to_f, "min lon"
606     assert_equal bbox[1], changeset['min_lat'].to_f, "min lat"
607     assert_equal bbox[2], changeset['max_lon'].to_f, "max lon"
608     assert_equal bbox[3], changeset['max_lat'].to_f, "max lat"
609   end
610
611   ##
612   # update the changeset_id of a way element
613   def update_changeset(xml, changeset_id)
614     xml_attr_rewrite(xml, 'changeset', changeset_id)
615   end
616
617   ##
618   # update an attribute in a way element
619   def xml_attr_rewrite(xml, name, value)
620     xml.find("//osm/way").first[name] = value.to_s
621     return xml
622   end
623
624 end