Fixed 'raw' raises by converting them to the appropriate OSM::APIError type. Made...
[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   # -----------------------
8   # Test simple changeset creation
9   # -----------------------
10   
11   def test_create
12     basic_authorization "test@openstreetmap.org", "test"
13     
14     # Create the first user's changeset
15     content "<osm><changeset>" +
16       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
17       "</changeset></osm>"
18     put :create
19     
20     assert_response :success, "Creation of changeset did not return sucess status"
21     newid = @response.body.to_i
22
23     # check end time, should be an hour ahead of creation time
24     cs = Changeset.find(newid)
25     duration = cs.closed_at - cs.created_at
26     # the difference can either be a rational, or a floating point number
27     # of seconds, depending on the code path taken :-(
28     if duration.class == Rational
29       assert_equal Rational(1,24), duration , "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
30     else
31       # must be number of seconds...
32       assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
33     end
34   end
35   
36   def test_create_invalid
37     basic_authorization "test@openstreetmap.org", "test"
38     content "<osm><changeset></osm>"
39     put :create
40     assert_response :bad_request, "creating a invalid changeset should fail"
41   end
42
43   def test_create_invalid_no_content
44     basic_authorization "test@openstreetmap.org", "test"
45     put :create
46     assert_response :bad_request, "creating a changeset with no content should fail"
47   end
48   
49   def test_create_wrong_method
50     basic_authorization "test@openstreetmap.org", "test"
51     get :create
52     assert_response :method_not_allowed
53   end
54     
55   ##
56   # check that the changeset can be read and returns the correct
57   # document structure.
58   def test_read
59     changeset_id = changesets(:normal_user_first_change).id
60     get :read, :id => changeset_id
61     assert_response :success, "cannot get first changeset"
62     
63     assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
64     assert_select "osm>changeset[id=#{changeset_id}]", 1
65   end
66   
67   ##
68   # test that the user who opened a change can close it
69   def test_close
70     basic_authorization "test@openstreetmap.org", "test"
71
72     cs_id = changesets(:normal_user_first_change).id
73     put :close, :id => cs_id
74     assert_response :success
75
76     # test that it really is closed now
77     cs = Changeset.find(cs_id)
78     assert(!cs.is_open?, 
79            "changeset should be closed now (#{cs.closed_at} > #{Time.now}.")
80   end
81
82   ##
83   # test that a different user can't close another user's changeset
84   def test_close_invalid
85     basic_authorization "test@example.com", "test"
86
87     put :close, :id => changesets(:normal_user_first_change).id
88     assert_response :conflict
89     assert_equal "The user doesn't own that changeset", @response.body
90   end
91
92   ##
93   # upload something simple, but valid and check that it can 
94   # be read back ok.
95   def test_upload_simple_valid
96     basic_authorization "test@openstreetmap.org", "test"
97
98     # simple diff to change a node, way and relation by removing 
99     # their tags
100     diff = <<EOF
101 <osmChange>
102  <modify>
103   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
104   <way id='1' changeset='1' version='1'>
105    <nd ref='3'/>
106   </way>
107  </modify>
108  <modify>
109   <relation id='1' changeset='1' version='1'>
110    <member type='way' role='some' ref='3'/>
111    <member type='node' role='some' ref='5'/>
112    <member type='relation' role='some' ref='3'/>
113   </relation>
114  </modify>
115 </osmChange>
116 EOF
117
118     # upload it
119     content diff
120     post :upload, :id => 1
121     assert_response :success, 
122       "can't upload a simple valid diff to changeset: #{@response.body}"
123
124     # check that the changes made it into the database
125     assert_equal 0, Node.find(1).tags.size, "node 1 should now have no tags"
126     assert_equal 0, Way.find(1).tags.size, "way 1 should now have no tags"
127     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
128   end
129     
130   ##
131   # upload something which creates new objects using placeholders
132   def test_upload_create_valid
133     basic_authorization "test@openstreetmap.org", "test"
134
135     # simple diff to create a node way and relation using placeholders
136     diff = <<EOF
137 <osmChange>
138  <create>
139   <node id='-1' lon='0' lat='0' changeset='1'>
140    <tag k='foo' v='bar'/>
141    <tag k='baz' v='bat'/>
142   </node>
143   <way id='-1' changeset='1'>
144    <nd ref='3'/>
145   </way>
146  </create>
147  <create>
148   <relation id='-1' changeset='1'>
149    <member type='way' role='some' ref='3'/>
150    <member type='node' role='some' ref='5'/>
151    <member type='relation' role='some' ref='3'/>
152   </relation>
153  </create>
154 </osmChange>
155 EOF
156
157     # upload it
158     content diff
159     post :upload, :id => 1
160     assert_response :success, 
161       "can't upload a simple valid creation to changeset: #{@response.body}"
162
163     # check the returned payload
164     assert_select "diffResult[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
165     assert_select "diffResult>node", 1
166     assert_select "diffresult>way", 1
167     assert_select "diffResult>relation", 1
168
169     # inspect the response to find out what the new element IDs are
170     doc = XML::Parser.string(@response.body).parse
171     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
172     new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
173     new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
174
175     # check the old IDs are all present and negative one
176     assert_equal -1, doc.find("//diffResult/node").first["old_id"].to_i
177     assert_equal -1, doc.find("//diffResult/way").first["old_id"].to_i
178     assert_equal -1, doc.find("//diffResult/relation").first["old_id"].to_i
179
180     # check the versions are present and equal one
181     assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
182     assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
183     assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
184
185     # check that the changes made it into the database
186     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
187     assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
188     assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
189   end
190     
191   ##
192   # test a complex delete where we delete elements which rely on eachother
193   # in the same transaction.
194   def test_upload_delete
195     basic_authorization "test@openstreetmap.org", "test"
196
197     diff = XML::Document.new
198     diff.root = XML::Node.new "osmChange"
199     delete = XML::Node.new "delete"
200     diff.root << delete
201     delete << current_relations(:visible_relation).to_xml_node
202     delete << current_relations(:used_relation).to_xml_node
203     delete << current_ways(:used_way).to_xml_node
204     delete << current_nodes(:node_used_by_relationship).to_xml_node
205
206     # upload it
207     content diff
208     post :upload, :id => 1
209     assert_response :success, 
210       "can't upload a deletion diff to changeset: #{@response.body}"
211
212     # check the response is well-formed
213     assert_select "diffResult>node", 1
214     assert_select "diffResult>way", 1
215     assert_select "diffResult>relation", 2
216
217     # check that everything was deleted
218     assert_equal false, Node.find(current_nodes(:node_used_by_relationship).id).visible
219     assert_equal false, Way.find(current_ways(:used_way).id).visible
220     assert_equal false, Relation.find(current_relations(:visible_relation).id).visible
221     assert_equal false, Relation.find(current_relations(:used_relation).id).visible
222   end
223
224   ##
225   # test uploading a delete with no lat/lon, as they are optional in
226   # the osmChange spec.
227   def test_upload_nolatlon_delete
228     basic_authorization "test@openstreetmap.org", "test"
229
230     node = current_nodes(:visible_node)
231     cs = changesets(:normal_user_first_change)
232     diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{cs.id}'/></delete></osmChange>"
233
234     # upload it
235     content diff
236     post :upload, :id => cs.id
237     assert_response :success, 
238       "can't upload a deletion diff to changeset: #{@response.body}"
239
240     # check the response is well-formed
241     assert_select "diffResult>node", 1
242
243     # check that everything was deleted
244     assert_equal false, Node.find(node.id).visible
245   end
246
247   def test_repeated_changeset_create
248     30.times do
249       basic_authorization "test@openstreetmap.org", "test"
250     
251       # create a temporary changeset
252       content "<osm><changeset>" +
253         "<tag k='created_by' v='osm test suite checking changesets'/>" + 
254         "</changeset></osm>"
255       assert_difference('Changeset.count', 1) do
256         put :create
257       end
258       assert_response :success
259       changeset_id = @response.body.to_i
260     end
261   end
262
263   ##
264   # test that deleting stuff in a transaction doesn't bypass the checks
265   # to ensure that used elements are not deleted.
266   def test_upload_delete_invalid
267     basic_authorization "test@openstreetmap.org", "test"
268
269     diff = XML::Document.new
270     diff.root = XML::Node.new "osmChange"
271     delete = XML::Node.new "delete"
272     diff.root << delete
273     delete << current_relations(:visible_relation).to_xml_node
274     delete << current_ways(:used_way).to_xml_node
275     delete << current_nodes(:node_used_by_relationship).to_xml_node
276
277     # upload it
278     content diff
279     post :upload, :id => 1
280     assert_response :precondition_failed, 
281       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
282
283     # check that nothing was, in fact, deleted
284     assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
285     assert_equal true, Way.find(current_ways(:used_way).id).visible
286     assert_equal true, Relation.find(current_relations(:visible_relation).id).visible
287   end
288
289   ##
290   # upload something which creates new objects and inserts them into
291   # existing containers using placeholders.
292   def test_upload_complex
293     basic_authorization "test@openstreetmap.org", "test"
294
295     # simple diff to create a node way and relation using placeholders
296     diff = <<EOF
297 <osmChange>
298  <create>
299   <node id='-1' lon='0' lat='0' changeset='1'>
300    <tag k='foo' v='bar'/>
301    <tag k='baz' v='bat'/>
302   </node>
303  </create>
304  <modify>
305   <way id='1' changeset='1' version='1'>
306    <nd ref='-1'/>
307    <nd ref='3'/>
308   </way>
309   <relation id='1' changeset='1' version='1'>
310    <member type='way' role='some' ref='3'/>
311    <member type='node' role='some' ref='-1'/>
312    <member type='relation' role='some' ref='3'/>
313   </relation>
314  </modify>
315 </osmChange>
316 EOF
317
318     # upload it
319     content diff
320     post :upload, :id => 1
321     assert_response :success, 
322       "can't upload a complex diff to changeset: #{@response.body}"
323
324     # check the returned payload
325     assert_select "diffResult[version=#{API_VERSION}][generator=\"#{GENERATOR}\"]", 1
326     assert_select "diffResult>node", 1
327     assert_select "diffResult>way", 1
328     assert_select "diffResult>relation", 1
329
330     # inspect the response to find out what the new element IDs are
331     doc = XML::Parser.string(@response.body).parse
332     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
333
334     # check that the changes made it into the database
335     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
336     assert_equal [new_node_id, 3], Way.find(1).nds, "way nodes should match"
337     Relation.find(1).members.each do |type,id,role|
338       if type == 'node'
339         assert_equal new_node_id, id, "relation should contain new node"
340       end
341     end
342   end
343     
344   ##
345   # create a diff which references several changesets, which should cause
346   # a rollback and none of the diff gets committed
347   def test_upload_invalid_changesets
348     basic_authorization "test@openstreetmap.org", "test"
349
350     # simple diff to create a node way and relation using placeholders
351     diff = <<EOF
352 <osmChange>
353  <modify>
354   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
355   <way id='1' changeset='1' version='1'>
356    <nd ref='3'/>
357   </way>
358  </modify>
359  <modify>
360   <relation id='1' changeset='1' version='1'>
361    <member type='way' role='some' ref='3'/>
362    <member type='node' role='some' ref='5'/>
363    <member type='relation' role='some' ref='3'/>
364   </relation>
365  </modify>
366  <create>
367   <node id='-1' lon='0' lat='0' changeset='4'>
368    <tag k='foo' v='bar'/>
369    <tag k='baz' v='bat'/>
370   </node>
371  </create>
372 </osmChange>
373 EOF
374     # cache the objects before uploading them
375     node = current_nodes(:visible_node)
376     way = current_ways(:visible_way)
377     rel = current_relations(:visible_relation)
378
379     # upload it
380     content diff
381     post :upload, :id => 1
382     assert_response :conflict, 
383       "uploading a diff with multiple changsets should have failed"
384
385     # check that objects are unmodified
386     assert_nodes_are_equal(node, Node.find(1))
387     assert_ways_are_equal(way, Way.find(1))
388   end
389     
390   ##
391   # upload multiple versions of the same element in the same diff.
392   def test_upload_multiple_valid
393     basic_authorization "test@openstreetmap.org", "test"
394
395     # change the location of a node multiple times, each time referencing
396     # the last version. doesn't this depend on version numbers being
397     # sequential?
398     diff = <<EOF
399 <osmChange>
400  <modify>
401   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
402   <node id='1' lon='1' lat='0' changeset='1' version='2'/>
403   <node id='1' lon='1' lat='1' changeset='1' version='3'/>
404   <node id='1' lon='1' lat='2' changeset='1' version='4'/>
405   <node id='1' lon='2' lat='2' changeset='1' version='5'/>
406   <node id='1' lon='3' lat='2' changeset='1' version='6'/>
407   <node id='1' lon='3' lat='3' changeset='1' version='7'/>
408   <node id='1' lon='9' lat='9' changeset='1' version='8'/>
409  </modify>
410 </osmChange>
411 EOF
412
413     # upload it
414     content diff
415     post :upload, :id => 1
416     assert_response :success, 
417       "can't upload multiple versions of an element in a diff: #{@response.body}"
418     
419     # check the response is well-formed. its counter-intuitive, but the
420     # API will return multiple elements with the same ID and different
421     # version numbers for each change we made.
422     assert_select "diffResult>node", 8
423   end
424
425   ##
426   # upload multiple versions of the same element in the same diff, but
427   # keep the version numbers the same.
428   def test_upload_multiple_duplicate
429     basic_authorization "test@openstreetmap.org", "test"
430
431     diff = <<EOF
432 <osmChange>
433  <modify>
434   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
435   <node id='1' lon='1' lat='1' changeset='1' version='1'/>
436  </modify>
437 </osmChange>
438 EOF
439
440     # upload it
441     content diff
442     post :upload, :id => 1
443     assert_response :conflict, 
444       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
445   end
446
447   ##
448   # try to upload some elements without specifying the version
449   def test_upload_missing_version
450     basic_authorization "test@openstreetmap.org", "test"
451
452     diff = <<EOF
453 <osmChange>
454  <modify>
455   <node id='1' lon='1' lat='1' changeset='1'/>
456  </modify>
457 </osmChange>
458 EOF
459
460     # upload it
461     content diff
462     post :upload, :id => 1
463     assert_response :bad_request, 
464       "shouldn't be able to upload an element without version: #{@response.body}"
465   end
466   
467   ##
468   # try to upload with commands other than create, modify, or delete
469   def test_action_upload_invalid
470     basic_authorization "test@openstreetmap.org", "test"
471     
472     diff = <<EOF
473 <osmChange>
474   <ping>
475     <node id='1' lon='1' lat='1' changeset='1' />
476   </ping>
477 </osmChange>
478 EOF
479   content diff
480   post :upload, :id => 1
481   assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
482   assert_equal @response.body, "Unknown action ping, choices are create, modify, delete."
483   end
484
485   ##
486   # upload a valid changeset which has a mixture of whitespace
487   # to check a bug reported by ivansanchez (#1565).
488   def test_upload_whitespace_valid
489     basic_authorization "test@openstreetmap.org", "test"
490
491     diff = <<EOF
492 <osmChange>
493  <modify><node id='1' lon='0' lat='0' changeset='1' 
494   version='1'></node>
495   <node id='1' lon='1' lat='1' changeset='1' version='2'><tag k='k' v='v'/></node></modify>
496  <modify>
497   <relation id='1' changeset='1' version='1'><member 
498    type='way' role='some' ref='3'/><member 
499     type='node' role='some' ref='5'/>
500    <member type='relation' role='some' ref='3'/>
501   </relation>
502  </modify></osmChange>
503 EOF
504
505     # upload it
506     content diff
507     post :upload, :id => 1
508     assert_response :success, 
509       "can't upload a valid diff with whitespace variations to changeset: #{@response.body}"
510
511     # check the response is well-formed
512     assert_select "diffResult>node", 2
513     assert_select "diffResult>relation", 1
514
515     # check that the changes made it into the database
516     assert_equal 1, Node.find(1).tags.size, "node 1 should now have one tag"
517     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
518   end
519
520   ##
521   # upload a valid changeset which has a mixture of whitespace
522   # to check a bug reported by ivansanchez.
523   def test_upload_reuse_placeholder_valid
524     basic_authorization "test@openstreetmap.org", "test"
525
526     diff = <<EOF
527 <osmChange>
528  <create>
529   <node id='-1' lon='0' lat='0' changeset='1'>
530    <tag k="foo" v="bar"/>
531   </node>
532  </create>
533  <modify>
534   <node id='-1' lon='1' lat='1' changeset='1' version='1'/>
535  </modify>
536  <delete>
537   <node id='-1' lon='2' lat='2' changeset='1' version='2'/>
538  </delete>
539 </osmChange>
540 EOF
541
542     # upload it
543     content diff
544     post :upload, :id => 1
545     assert_response :success, 
546       "can't upload a valid diff with re-used placeholders to changeset: #{@response.body}"
547
548     # check the response is well-formed
549     assert_select "diffResult>node", 3
550     assert_select "diffResult>node[old_id=-1]", 3
551   end
552
553   ##
554   # test what happens if a diff upload re-uses placeholder IDs in an
555   # illegal way.
556   def test_upload_placeholder_invalid
557     basic_authorization "test@openstreetmap.org", "test"
558
559     diff = <<EOF
560 <osmChange>
561  <create>
562   <node id='-1' lon='0' lat='0' changeset='1' version='1'/>
563   <node id='-1' lon='1' lat='1' changeset='1' version='1'/>
564   <node id='-1' lon='2' lat='2' changeset='1' version='2'/>
565  </create>
566 </osmChange>
567 EOF
568
569     # upload it
570     content diff
571     post :upload, :id => 1
572     assert_response :bad_request, 
573       "shouldn't be able to re-use placeholder IDs"
574   end
575
576   ##
577   # test that uploading a way referencing invalid placeholders gives a 
578   # proper error, not a 500.
579   def test_upload_placeholder_invalid_way
580     basic_authorization "test@example.com", "test"
581
582     diff = <<EOF
583 <osmChange>
584  <create>
585   <node id="-1" lon="0" lat="0" changeset="2" version="1"/>
586   <node id="-2" lon="1" lat="1" changeset="2" version="1"/>
587   <node id="-3" lon="2" lat="2" changeset="2" version="1"/>
588   <way id="-1" changeset="2" version="1">
589    <nd ref="-1"/>
590    <nd ref="-2"/>
591    <nd ref="-3"/>
592    <nd ref="-4"/>
593   </way>
594  </create>
595 </osmChange>
596 EOF
597
598     # upload it
599     content diff
600     post :upload, :id => 2
601     assert_response :bad_request, 
602       "shouldn't be able to use invalid placeholder IDs"
603     assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
604
605     # the same again, but this time use an existing way
606     diff = <<EOF
607 <osmChange>
608  <create>
609   <node id="-1" lon="0" lat="0" changeset="2" version="1"/>
610   <node id="-2" lon="1" lat="1" changeset="2" version="1"/>
611   <node id="-3" lon="2" lat="2" changeset="2" version="1"/>
612   <way id="1" changeset="2" version="1">
613    <nd ref="-1"/>
614    <nd ref="-2"/>
615    <nd ref="-3"/>
616    <nd ref="-4"/>
617   </way>
618  </create>
619 </osmChange>
620 EOF
621
622     # upload it
623     content diff
624     post :upload, :id => 2
625     assert_response :bad_request, 
626       "shouldn't be able to use invalid placeholder IDs"
627     assert_equal "Placeholder node not found for reference -4 in way 1", @response.body
628   end
629
630   ##
631   # test that uploading a relation referencing invalid placeholders gives a 
632   # proper error, not a 500.
633   def test_upload_placeholder_invalid_relation
634     basic_authorization "test@example.com", "test"
635
636     diff = <<EOF
637 <osmChange>
638  <create>
639   <node id="-1" lon="0" lat="0" changeset="2" version="1"/>
640   <node id="-2" lon="1" lat="1" changeset="2" version="1"/>
641   <node id="-3" lon="2" lat="2" changeset="2" version="1"/>
642   <relation id="-1" changeset="2" version="1">
643    <member type="node" role="foo" ref="-1"/>
644    <member type="node" role="foo" ref="-2"/>
645    <member type="node" role="foo" ref="-3"/>
646    <member type="node" role="foo" ref="-4"/>
647   </relation>
648  </create>
649 </osmChange>
650 EOF
651
652     # upload it
653     content diff
654     post :upload, :id => 2
655     assert_response :bad_request, 
656       "shouldn't be able to use invalid placeholder IDs"
657     assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
658
659     # the same again, but this time use an existing way
660     diff = <<EOF
661 <osmChange>
662  <create>
663   <node id="-1" lon="0" lat="0" changeset="2" version="1"/>
664   <node id="-2" lon="1" lat="1" changeset="2" version="1"/>
665   <node id="-3" lon="2" lat="2" changeset="2" version="1"/>
666   <relation id="1" changeset="2" version="1">
667    <member type="node" role="foo" ref="-1"/>
668    <member type="node" role="foo" ref="-2"/>
669    <member type="node" role="foo" ref="-3"/>
670    <member type="way" role="bar" ref="-1"/>
671   </relation>
672  </create>
673 </osmChange>
674 EOF
675
676     # upload it
677     content diff
678     post :upload, :id => 2
679     assert_response :bad_request, 
680       "shouldn't be able to use invalid placeholder IDs"
681     assert_equal "Placeholder Way not found for reference -1 in relation 1.", @response.body
682   end
683
684   ##
685   # test what happens if a diff is uploaded containing only a node
686   # move.
687   def test_upload_node_move
688     basic_authorization "test@openstreetmap.org", "test"
689
690     content "<osm><changeset>" +
691       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
692       "</changeset></osm>"
693     put :create
694     assert_response :success
695     changeset_id = @response.body.to_i
696
697     old_node = current_nodes(:visible_node)
698
699     diff = XML::Document.new
700     diff.root = XML::Node.new "osmChange"
701     modify = XML::Node.new "modify"
702     xml_old_node = old_node.to_xml_node
703     xml_old_node["lat"] = (2.0).to_s
704     xml_old_node["lon"] = (2.0).to_s
705     xml_old_node["changeset"] = changeset_id.to_s
706     modify << xml_old_node
707     diff.root << modify
708
709     # upload it
710     content diff
711     post :upload, :id => changeset_id
712     assert_response :success, 
713       "diff should have uploaded OK"
714
715     # check the bbox
716     changeset = Changeset.find(changeset_id)
717     assert_equal 1*SCALE, changeset.min_lon, "min_lon should be 1 degree"
718     assert_equal 2*SCALE, changeset.max_lon, "max_lon should be 2 degrees"
719     assert_equal 1*SCALE, changeset.min_lat, "min_lat should be 1 degree"
720     assert_equal 2*SCALE, changeset.max_lat, "max_lat should be 2 degrees"
721   end
722
723   ##
724   # test what happens if a diff is uploaded adding a node to a way.
725   def test_upload_way_extend
726     basic_authorization "test@openstreetmap.org", "test"
727
728     content "<osm><changeset>" +
729       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
730       "</changeset></osm>"
731     put :create
732     assert_response :success
733     changeset_id = @response.body.to_i
734
735     old_way = current_ways(:visible_way)
736
737     diff = XML::Document.new
738     diff.root = XML::Node.new "osmChange"
739     modify = XML::Node.new "modify"
740     xml_old_way = old_way.to_xml_node
741     nd_ref = XML::Node.new "nd"
742     nd_ref["ref"] = current_nodes(:visible_node).id.to_s
743     xml_old_way << nd_ref
744     xml_old_way["changeset"] = changeset_id.to_s
745     modify << xml_old_way
746     diff.root << modify
747
748     # upload it
749     content diff
750     post :upload, :id => changeset_id
751     assert_response :success, 
752       "diff should have uploaded OK"
753
754     # check the bbox
755     changeset = Changeset.find(changeset_id)
756     assert_equal 1*SCALE, changeset.min_lon, "min_lon should be 1 degree"
757     assert_equal 3*SCALE, changeset.max_lon, "max_lon should be 3 degrees"
758     assert_equal 1*SCALE, changeset.min_lat, "min_lat should be 1 degree"
759     assert_equal 3*SCALE, changeset.max_lat, "max_lat should be 3 degrees"
760   end
761
762   ##
763   # test for more issues in #1568
764   def test_upload_empty_invalid
765     basic_authorization "test@openstreetmap.org", "test"
766
767     [ "<osmChange/>",
768       "<osmChange></osmChange>",
769       "<osmChange><modify/></osmChange>",
770       "<osmChange><modify></modify></osmChange>"
771     ].each do |diff|
772       # upload it
773       content diff
774       post :upload, :id => 1
775       assert_response(:success, "should be able to upload " +
776                       "empty changeset: " + diff)
777     end
778   end
779
780   ##
781   # when we make some simple changes we get the same changes back from the 
782   # diff download.
783   def test_diff_download_simple
784     basic_authorization(users(:normal_user).email, "test")
785
786     # create a temporary changeset
787     content "<osm><changeset>" +
788       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
789       "</changeset></osm>"
790     put :create
791     assert_response :success
792     changeset_id = @response.body.to_i
793
794     # add a diff to it
795     diff = <<EOF
796 <osmChange>
797  <modify>
798   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
799   <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
800   <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
801   <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
802   <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
803   <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
804   <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
805   <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
806  </modify>
807 </osmChange>
808 EOF
809
810     # upload it
811     content diff
812     post :upload, :id => changeset_id
813     assert_response :success, 
814       "can't upload multiple versions of an element in a diff: #{@response.body}"
815     
816     get :download, :id => changeset_id
817     assert_response :success
818
819     assert_select "osmChange", 1
820     assert_select "osmChange>modify", 8
821     assert_select "osmChange>modify>node", 8
822   end
823   
824   ##
825   # culled this from josm to ensure that nothing in the way that josm
826   # is formatting the request is causing it to fail.
827   #
828   # NOTE: the error turned out to be something else completely!
829   def test_josm_upload
830     basic_authorization(users(:normal_user).email, "test")
831
832     # create a temporary changeset
833     content "<osm><changeset>" +
834       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
835       "</changeset></osm>"
836     put :create
837     assert_response :success
838     changeset_id = @response.body.to_i
839
840     diff = <<OSM
841 <osmChange version="0.6" generator="JOSM">
842 <create version="0.6" generator="JOSM">
843   <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
844   <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
845   <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
846   <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
847   <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
848   <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
849   <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
850   <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
851   <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
852   <way id='-10' action='modiy' visible='true' changeset='#{changeset_id}'>
853     <nd ref='-1' />
854     <nd ref='-2' />
855     <nd ref='-3' />
856     <nd ref='-4' />
857     <nd ref='-5' />
858     <nd ref='-6' />
859     <nd ref='-7' />
860     <nd ref='-8' />
861     <nd ref='-9' />
862     <tag k='highway' v='residential' />
863     <tag k='name' v='Foobar Street' />
864   </way>
865 </create>
866 </osmChange>
867 OSM
868
869     # upload it
870     content diff
871     post :upload, :id => changeset_id
872     assert_response :success, 
873       "can't upload a diff from JOSM: #{@response.body}"
874     
875     get :download, :id => changeset_id
876     assert_response :success
877
878     assert_select "osmChange", 1
879     assert_select "osmChange>create>node", 9
880     assert_select "osmChange>create>way", 1
881     assert_select "osmChange>create>way>nd", 9
882     assert_select "osmChange>create>way>tag", 2
883   end
884
885   ##
886   # when we make some complex changes we get the same changes back from the 
887   # diff download.
888   def test_diff_download_complex
889     basic_authorization(users(:normal_user).email, "test")
890
891     # create a temporary changeset
892     content "<osm><changeset>" +
893       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
894       "</changeset></osm>"
895     put :create
896     assert_response :success
897     changeset_id = @response.body.to_i
898
899     # add a diff to it
900     diff = <<EOF
901 <osmChange>
902  <delete>
903   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
904  </delete>
905  <create>
906   <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
907   <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
908   <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
909  </create>
910  <modify>
911   <node id='3' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
912   <way id='1' changeset='#{changeset_id}' version='1'>
913    <nd ref='3'/>
914    <nd ref='-1'/>
915    <nd ref='-2'/>
916    <nd ref='-3'/>
917   </way>
918  </modify>
919 </osmChange>
920 EOF
921
922     # upload it
923     content diff
924     post :upload, :id => changeset_id
925     assert_response :success, 
926       "can't upload multiple versions of an element in a diff: #{@response.body}"
927     
928     get :download, :id => changeset_id
929     assert_response :success
930
931     assert_select "osmChange", 1
932     assert_select "osmChange>create", 3
933     assert_select "osmChange>delete", 1
934     assert_select "osmChange>modify", 2
935     assert_select "osmChange>create>node", 3
936     assert_select "osmChange>delete>node", 1 
937     assert_select "osmChange>modify>node", 1
938     assert_select "osmChange>modify>way", 1
939   end
940
941   ##
942   # check that the bounding box of a changeset gets updated correctly
943   def test_changeset_bbox
944     basic_authorization "test@openstreetmap.org", "test"
945
946     # create a new changeset
947     content "<osm><changeset/></osm>"
948     put :create
949     assert_response :success, "Creating of changeset failed."
950     changeset_id = @response.body.to_i
951     
952     # add a single node to it
953     with_controller(NodeController.new) do
954       content "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
955       put :create
956       assert_response :success, "Couldn't create node."
957     end
958
959     # get the bounding box back from the changeset
960     get :read, :id => changeset_id
961     assert_response :success, "Couldn't read back changeset."
962     assert_select "osm>changeset[min_lon=1.0]", 1
963     assert_select "osm>changeset[max_lon=1.0]", 1
964     assert_select "osm>changeset[min_lat=2.0]", 1
965     assert_select "osm>changeset[max_lat=2.0]", 1
966
967     # add another node to it
968     with_controller(NodeController.new) do
969       content "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
970       put :create
971       assert_response :success, "Couldn't create second node."
972     end
973
974     # get the bounding box back from the changeset
975     get :read, :id => changeset_id
976     assert_response :success, "Couldn't read back changeset for the second time."
977     assert_select "osm>changeset[min_lon=1.0]", 1
978     assert_select "osm>changeset[max_lon=2.0]", 1
979     assert_select "osm>changeset[min_lat=1.0]", 1
980     assert_select "osm>changeset[max_lat=2.0]", 1
981
982     # add (delete) a way to it, which contains a point at (3,3)
983     with_controller(WayController.new) do
984       content update_changeset(current_ways(:visible_way).to_xml,
985                                changeset_id)
986       put :delete, :id => current_ways(:visible_way).id
987       assert_response :success, "Couldn't delete a way."
988     end
989
990     # get the bounding box back from the changeset
991     get :read, :id => changeset_id
992     assert_response :success, "Couldn't read back changeset for the third time."
993     # note that the 3.1 here is because of the bbox overexpansion
994     assert_select "osm>changeset[min_lon=1.0]", 1
995     assert_select "osm>changeset[max_lon=3.1]", 1
996     assert_select "osm>changeset[min_lat=1.0]", 1
997     assert_select "osm>changeset[max_lat=3.1]", 1    
998   end
999
1000   ##
1001   # test that the changeset :include method works as it should
1002   def test_changeset_include
1003     basic_authorization "test@openstreetmap.org", "test"
1004
1005     # create a new changeset
1006     content "<osm><changeset/></osm>"
1007     put :create
1008     assert_response :success, "Creating of changeset failed."
1009     changeset_id = @response.body.to_i
1010
1011     # NOTE: the include method doesn't over-expand, like inserting
1012     # a real method does. this is because we expect the client to 
1013     # know what it is doing!
1014     check_after_include(changeset_id,  1,  1, [ 1,  1,  1,  1])
1015     check_after_include(changeset_id,  3,  3, [ 1,  1,  3,  3])
1016     check_after_include(changeset_id,  4,  2, [ 1,  1,  4,  3])
1017     check_after_include(changeset_id,  2,  2, [ 1,  1,  4,  3])
1018     check_after_include(changeset_id, -1, -1, [-1, -1,  4,  3])
1019     check_after_include(changeset_id, -2,  5, [-2, -1,  4,  5])
1020   end
1021
1022   ##
1023   # test the query functionality of changesets
1024   def test_query
1025     get :query, :bbox => "-10,-10, 10, 10"
1026     assert_response :success, "can't get changesets in bbox"
1027     assert_changesets [1,4,6]
1028
1029     get :query, :bbox => "4.5,4.5,4.6,4.6"
1030     assert_response :success, "can't get changesets in bbox"
1031     assert_changesets [1]
1032
1033     # can't get changesets of user 1 without authenticating
1034     get :query, :user => users(:normal_user).id
1035     assert_response :not_found, "shouldn't be able to get changesets by non-public user"
1036
1037     # but this should work
1038     basic_authorization "test@openstreetmap.org", "test"
1039     get :query, :user => users(:normal_user).id
1040     assert_response :success, "can't get changesets by user"
1041     assert_changesets [1,3,4,6]
1042
1043     get :query, :user => users(:normal_user).id, :open => true
1044     assert_response :success, "can't get changesets by user and open"
1045     assert_changesets [1,4]
1046
1047     get :query, :time => '2007-12-31'
1048     assert_response :success, "can't get changesets by time-since"
1049     assert_changesets [1,2,4,5,6]
1050
1051     get :query, :time => '2008-01-01T12:34Z'
1052     assert_response :success, "can't get changesets by time-since with hour"
1053     assert_changesets [1,2,4,5,6]
1054
1055     get :query, :time => '2007-12-31T23:59Z,2008-01-01T00:01Z'
1056     assert_response :success, "can't get changesets by time-range"
1057     assert_changesets [1,4,5,6]
1058
1059     get :query, :open => 'true'
1060     assert_response :success, "can't get changesets by open-ness"
1061     assert_changesets [1,2,4]
1062   end
1063
1064   ##
1065   # check that errors are returned if garbage is inserted 
1066   # into query strings
1067   def test_query_invalid
1068     [ "abracadabra!",
1069       "1,2,3,F",
1070       ";drop table users;"
1071       ].each do |bbox|
1072       get :query, :bbox => bbox
1073       assert_response :bad_request, "'#{bbox}' isn't a bbox"
1074     end
1075
1076     [ "now()",
1077       "00-00-00",
1078       ";drop table users;",
1079       ",",
1080       "-,-"
1081       ].each do |time|
1082       get :query, :time => time
1083       assert_response :bad_request, "'#{time}' isn't a valid time range"
1084     end
1085
1086     [ "me",
1087       "foobar",
1088       "-1",
1089       "0"
1090       ].each do |uid|
1091       get :query, :user => uid
1092       assert_response :bad_request, "'#{uid}' isn't a valid user ID"
1093     end
1094   end
1095
1096   ##
1097   # check updating tags on a changeset
1098   def test_changeset_update
1099     changeset = changesets(:normal_user_first_change)
1100     new_changeset = changeset.to_xml
1101     new_tag = XML::Node.new "tag"
1102     new_tag['k'] = "tagtesting"
1103     new_tag['v'] = "valuetesting"
1104     new_changeset.find("//osm/changeset").first << new_tag
1105     content new_changeset
1106
1107     # try without any authorization
1108     put :update, :id => changeset.id
1109     assert_response :unauthorized
1110
1111     # try with the wrong authorization
1112     basic_authorization "test@example.com", "test"
1113     put :update, :id => changeset.id
1114     assert_response :conflict
1115
1116     # now this should work...
1117     basic_authorization "test@openstreetmap.org", "test"
1118     put :update, :id => changeset.id
1119     assert_response :success
1120
1121     assert_select "osm>changeset[id=#{changeset.id}]", 1
1122     assert_select "osm>changeset>tag", 2
1123     assert_select "osm>changeset>tag[k=tagtesting][v=valuetesting]", 1
1124   end
1125   
1126   ##
1127   # check that a user different from the one who opened the changeset
1128   # can't modify it.
1129   def test_changeset_update_invalid
1130     basic_authorization "test@example.com", "test"
1131
1132     changeset = changesets(:normal_user_first_change)
1133     new_changeset = changeset.to_xml
1134     new_tag = XML::Node.new "tag"
1135     new_tag['k'] = "testing"
1136     new_tag['v'] = "testing"
1137     new_changeset.find("//osm/changeset").first << new_tag
1138
1139     content new_changeset
1140     put :update, :id => changeset.id
1141     assert_response :conflict
1142   end
1143
1144   ##
1145   # check that a changeset can contain a certain max number of changes.
1146   def test_changeset_limits
1147     basic_authorization "test@openstreetmap.org", "test"
1148
1149     # open a new changeset
1150     content "<osm><changeset/></osm>"
1151     put :create
1152     assert_response :success, "can't create a new changeset"
1153     cs_id = @response.body.to_i
1154
1155     # start the counter just short of where the changeset should finish.
1156     offset = 10
1157     # alter the database to set the counter on the changeset directly, 
1158     # otherwise it takes about 6 minutes to fill all of them.
1159     changeset = Changeset.find(cs_id)
1160     changeset.num_changes = Changeset::MAX_ELEMENTS - offset
1161     changeset.save!
1162
1163     with_controller(NodeController.new) do
1164       # create a new node
1165       content "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
1166       put :create
1167       assert_response :success, "can't create a new node"
1168       node_id = @response.body.to_i
1169
1170       get :read, :id => node_id
1171       assert_response :success, "can't read back new node"
1172       node_doc = XML::Parser.string(@response.body).parse
1173       node_xml = node_doc.find("//osm/node").first
1174
1175       # loop until we fill the changeset with nodes
1176       offset.times do |i|
1177         node_xml['lat'] = rand.to_s
1178         node_xml['lon'] = rand.to_s
1179         node_xml['version'] = (i+1).to_s
1180
1181         content node_doc
1182         put :update, :id => node_id
1183         assert_response :success, "attempt #{i} should have succeeded"
1184       end
1185
1186       # trying again should fail
1187       node_xml['lat'] = rand.to_s
1188       node_xml['lon'] = rand.to_s
1189       node_xml['version'] = offset.to_s
1190       
1191       content node_doc
1192       put :update, :id => node_id
1193       assert_response :conflict, "final attempt should have failed"
1194     end
1195
1196     changeset = Changeset.find(cs_id)
1197     assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
1198
1199     # check that the changeset is now closed as well
1200     assert(!changeset.is_open?, 
1201            "changeset should have been auto-closed by exceeding " + 
1202            "element limit.")
1203   end
1204   
1205   # This should display the last 20 changesets closed.
1206   def test_list
1207     @changesets = Changeset.find(:all, :order => "created_at DESC", :conditions => ['min_lat IS NOT NULL'], :limit=> 20)
1208     assert @changesets.size <= 20
1209     get :list
1210     assert_response :success
1211     assert_template "list"
1212     # Now check that all 20 (or however many were returned) changesets are in the html
1213     assert_select "h1", :text => "Recent Changes", :count => 1
1214     assert_select "table[id='keyvalue'] tr", :count => @changesets.size + 1
1215     @changesets.each do |changeset|
1216       # FIXME this test needs rewriting - test for table contents
1217     end
1218   end
1219   
1220   #------------------------------------------------------------
1221   # utility functions
1222   #------------------------------------------------------------
1223
1224   ##
1225   # boilerplate for checking that certain changesets exist in the
1226   # output.
1227   def assert_changesets(ids)
1228     assert_select "osm>changeset", ids.size
1229     ids.each do |id|
1230       assert_select "osm>changeset[id=#{id}]", 1
1231     end
1232   end
1233
1234   ##
1235   # call the include method and assert properties of the bbox
1236   def check_after_include(changeset_id, lon, lat, bbox)
1237     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
1238     post :expand_bbox, :id => changeset_id
1239     assert_response :success, "Setting include of changeset failed: #{@response.body}"
1240
1241     # check exactly one changeset
1242     assert_select "osm>changeset", 1
1243     assert_select "osm>changeset[id=#{changeset_id}]", 1
1244
1245     # check the bbox
1246     doc = XML::Parser.string(@response.body).parse
1247     changeset = doc.find("//osm/changeset").first
1248     assert_equal bbox[0], changeset['min_lon'].to_f, "min lon"
1249     assert_equal bbox[1], changeset['min_lat'].to_f, "min lat"
1250     assert_equal bbox[2], changeset['max_lon'].to_f, "max lon"
1251     assert_equal bbox[3], changeset['max_lat'].to_f, "max lat"
1252   end
1253
1254   ##
1255   # update the changeset_id of a way element
1256   def update_changeset(xml, changeset_id)
1257     xml_attr_rewrite(xml, 'changeset', changeset_id)
1258   end
1259
1260   ##
1261   # update an attribute in a way element
1262   def xml_attr_rewrite(xml, name, value)
1263     xml.find("//osm/way").first[name] = value.to_s
1264     return xml
1265   end
1266
1267 end