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