Test and fix for issue #1568. Wasn't testing for end element in the right place.
[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.0, duration , "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 that deleting stuff in a transaction doesn't bypass the checks
222   # to ensure that used elements are not deleted.
223   def test_upload_delete_invalid
224     basic_authorization "test@openstreetmap.org", "test"
225
226     diff = XML::Document.new
227     diff.root = XML::Node.new "osmChange"
228     delete = XML::Node.new "delete"
229     diff.root << delete
230     delete << current_relations(:visible_relation).to_xml_node
231     delete << current_ways(:used_way).to_xml_node
232     delete << current_nodes(:node_used_by_relationship).to_xml_node
233
234     # upload it
235     content diff
236     post :upload, :id => 1
237     assert_response :precondition_failed, 
238       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
239
240     # check that nothing was, in fact, deleted
241     assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
242     assert_equal true, Way.find(current_ways(:used_way).id).visible
243     assert_equal true, Relation.find(current_relations(:visible_relation).id).visible
244   end
245
246   ##
247   # upload something which creates new objects and inserts them into
248   # existing containers using placeholders.
249   def test_upload_complex
250     basic_authorization "test@openstreetmap.org", "test"
251
252     # simple diff to create a node way and relation using placeholders
253     diff = <<EOF
254 <osmChange>
255  <create>
256   <node id='-1' lon='0' lat='0' changeset='1'>
257    <tag k='foo' v='bar'/>
258    <tag k='baz' v='bat'/>
259   </node>
260  </create>
261  <modify>
262   <way id='1' changeset='1' version='1'>
263    <nd ref='-1'/>
264    <nd ref='3'/>
265   </way>
266   <relation id='1' changeset='1' version='1'>
267    <member type='way' role='some' ref='3'/>
268    <member type='node' role='some' ref='-1'/>
269    <member type='relation' role='some' ref='3'/>
270   </relation>
271  </modify>
272 </osmChange>
273 EOF
274
275     # upload it
276     content diff
277     post :upload, :id => 1
278     assert_response :success, 
279       "can't upload a complex diff to changeset: #{@response.body}"
280
281     # check the returned payload
282     assert_select "diffResult[version=#{API_VERSION}][generator=\"#{GENERATOR}\"]", 1
283     assert_select "diffResult>node", 1
284     assert_select "diffResult>way", 1
285     assert_select "diffResult>relation", 1
286
287     # inspect the response to find out what the new element IDs are
288     doc = XML::Parser.string(@response.body).parse
289     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
290
291     # check that the changes made it into the database
292     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
293     assert_equal [new_node_id, 3], Way.find(1).nds, "way nodes should match"
294     Relation.find(1).members.each do |type,id,role|
295       if type == 'node'
296         assert_equal new_node_id, id, "relation should contain new node"
297       end
298     end
299   end
300     
301   ##
302   # create a diff which references several changesets, which should cause
303   # a rollback and none of the diff gets committed
304   def test_upload_invalid_changesets
305     basic_authorization "test@openstreetmap.org", "test"
306
307     # simple diff to create a node way and relation using placeholders
308     diff = <<EOF
309 <osmChange>
310  <modify>
311   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
312   <way id='1' changeset='1' version='1'>
313    <nd ref='3'/>
314   </way>
315  </modify>
316  <modify>
317   <relation id='1' changeset='1' version='1'>
318    <member type='way' role='some' ref='3'/>
319    <member type='node' role='some' ref='5'/>
320    <member type='relation' role='some' ref='3'/>
321   </relation>
322  </modify>
323  <create>
324   <node id='-1' lon='0' lat='0' changeset='4'>
325    <tag k='foo' v='bar'/>
326    <tag k='baz' v='bat'/>
327   </node>
328  </create>
329 </osmChange>
330 EOF
331     # cache the objects before uploading them
332     node = current_nodes(:visible_node)
333     way = current_ways(:visible_way)
334     rel = current_relations(:visible_relation)
335
336     # upload it
337     content diff
338     post :upload, :id => 1
339     assert_response :conflict, 
340       "uploading a diff with multiple changsets should have failed"
341
342     # check that objects are unmodified
343     assert_nodes_are_equal(node, Node.find(1))
344     assert_ways_are_equal(way, Way.find(1))
345   end
346     
347   ##
348   # upload multiple versions of the same element in the same diff.
349   def test_upload_multiple_valid
350     basic_authorization "test@openstreetmap.org", "test"
351
352     # change the location of a node multiple times, each time referencing
353     # the last version. doesn't this depend on version numbers being
354     # sequential?
355     diff = <<EOF
356 <osmChange>
357  <modify>
358   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
359   <node id='1' lon='1' lat='0' changeset='1' version='2'/>
360   <node id='1' lon='1' lat='1' changeset='1' version='3'/>
361   <node id='1' lon='1' lat='2' changeset='1' version='4'/>
362   <node id='1' lon='2' lat='2' changeset='1' version='5'/>
363   <node id='1' lon='3' lat='2' changeset='1' version='6'/>
364   <node id='1' lon='3' lat='3' changeset='1' version='7'/>
365   <node id='1' lon='9' lat='9' changeset='1' version='8'/>
366  </modify>
367 </osmChange>
368 EOF
369
370     # upload it
371     content diff
372     post :upload, :id => 1
373     assert_response :success, 
374       "can't upload multiple versions of an element in a diff: #{@response.body}"
375     
376     # check the response is well-formed. its counter-intuitive, but the
377     # API will return multiple elements with the same ID and different
378     # version numbers for each change we made.
379     assert_select "diffResult>node", 8
380   end
381
382   ##
383   # upload multiple versions of the same element in the same diff, but
384   # keep the version numbers the same.
385   def test_upload_multiple_duplicate
386     basic_authorization "test@openstreetmap.org", "test"
387
388     diff = <<EOF
389 <osmChange>
390  <modify>
391   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
392   <node id='1' lon='1' lat='1' changeset='1' version='1'/>
393  </modify>
394 </osmChange>
395 EOF
396
397     # upload it
398     content diff
399     post :upload, :id => 1
400     assert_response :conflict, 
401       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
402   end
403
404   ##
405   # try to upload some elements without specifying the version
406   def test_upload_missing_version
407     basic_authorization "test@openstreetmap.org", "test"
408
409     diff = <<EOF
410 <osmChange>
411  <modify>
412   <node id='1' lon='1' lat='1' changeset='1'/>
413  </modify>
414 </osmChange>
415 EOF
416
417     # upload it
418     content diff
419     post :upload, :id => 1
420     assert_response :bad_request, 
421       "shouldn't be able to upload an element without version: #{@response.body}"
422   end
423   
424   ##
425   # try to upload with commands other than create, modify, or delete
426   def test_action_upload_invalid
427     basic_authorization "test@openstreetmap.org", "test"
428     
429     diff = <<EOF
430 <osmChange>
431   <ping>
432     <node id='1' lon='1' lat='1' changeset='1' />
433   </ping>
434 </osmChange>
435 EOF
436   content diff
437   post :upload, :id => 1
438   assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
439   assert_equal @response.body, "Unknown action ping, choices are create, modify, delete."
440   end
441
442   ##
443   # upload a valid changeset which has a mixture of whitespace
444   # to check a bug reported by ivansanchez (#1565).
445   def test_upload_whitespace_valid
446     basic_authorization "test@openstreetmap.org", "test"
447
448     diff = <<EOF
449 <osmChange>
450  <modify><node id='1' lon='0' lat='0' changeset='1' 
451   version='1'></node>
452   <node id='1' lon='1' lat='1' changeset='1' version='2'><tag k='k' v='v'/></node></modify>
453  <modify>
454   <relation id='1' changeset='1' version='1'><member 
455    type='way' role='some' ref='3'/><member 
456     type='node' role='some' ref='5'/>
457    <member type='relation' role='some' ref='3'/>
458   </relation>
459  </modify></osmChange>
460 EOF
461
462     # upload it
463     content diff
464     post :upload, :id => 1
465     assert_response :success, 
466       "can't upload a valid diff with whitespace variations to changeset: #{@response.body}"
467
468     # check the response is well-formed
469     assert_select "diffResult>node", 2
470     assert_select "diffResult>relation", 1
471
472     # check that the changes made it into the database
473     assert_equal 1, Node.find(1).tags.size, "node 1 should now have one tag"
474     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
475   end
476
477   ##
478   # upload a valid changeset which has a mixture of whitespace
479   # to check a bug reported by ivansanchez.
480   def test_upload_reuse_placeholder_valid
481     basic_authorization "test@openstreetmap.org", "test"
482
483     diff = <<EOF
484 <osmChange>
485  <create>
486   <node id='-1' lon='0' lat='0' changeset='1'>
487    <tag k="foo" v="bar"/>
488   </node>
489  </create>
490  <modify>
491   <node id='-1' lon='1' lat='1' changeset='1' version='1'/>
492  </modify>
493  <delete>
494   <node id='-1' lon='2' lat='2' changeset='1' version='2'/>
495  </delete>
496 </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 re-used placeholders to changeset: #{@response.body}"
504
505     # check the response is well-formed
506     assert_select "diffResult>node", 3
507     assert_select "diffResult>node[old_id=-1]", 3
508   end
509
510   ##
511   # test what happens if a diff upload re-uses placeholder IDs in an
512   # illegal way.
513   def test_upload_placeholder_invalid
514     basic_authorization "test@openstreetmap.org", "test"
515
516     diff = <<EOF
517 <osmChange>
518  <create>
519   <node id='-1' lon='0' lat='0' changeset='1' version='1'/>
520   <node id='-1' lon='1' lat='1' changeset='1' version='1'/>
521   <node id='-1' lon='2' lat='2' changeset='1' version='2'/>
522  </create>
523 </osmChange>
524 EOF
525
526     # upload it
527     content diff
528     post :upload, :id => 1
529     assert_response :bad_request, 
530       "shouldn't be able to re-use placeholder IDs"
531   end
532
533   ##
534   # test for more issues in #1568
535   def test_upload_empty_invalid
536     basic_authorization "test@openstreetmap.org", "test"
537
538     [ "<osmChange/>",
539       "<osmChange></osmChange>",
540       "<osmChange><modify/></osmChange>",
541       "<osmChange><modify></modify></osmChange>"
542     ].each do |diff|
543       # upload it
544       content diff
545       post :upload, :id => 1
546       assert_response(:success, "should be able to upload " +
547                       "empty changeset: " + diff)
548     end
549   end
550
551   ##
552   # when we make some simple changes we get the same changes back from the 
553   # diff download.
554   def test_diff_download_simple
555     basic_authorization(users(:normal_user).email, "test")
556
557     # create a temporary changeset
558     content "<osm><changeset>" +
559       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
560       "</changeset></osm>"
561     put :create
562     assert_response :success
563     changeset_id = @response.body.to_i
564
565     # add a diff to it
566     diff = <<EOF
567 <osmChange>
568  <modify>
569   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
570   <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
571   <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
572   <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
573   <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
574   <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
575   <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
576   <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
577  </modify>
578 </osmChange>
579 EOF
580
581     # upload it
582     content diff
583     post :upload, :id => changeset_id
584     assert_response :success, 
585       "can't upload multiple versions of an element in a diff: #{@response.body}"
586     
587     get :download, :id => changeset_id
588     assert_response :success
589
590     assert_select "osmChange", 1
591     assert_select "osmChange>modify", 8
592     assert_select "osmChange>modify>node", 8
593   end
594   
595   ##
596   # culled this from josm to ensure that nothing in the way that josm
597   # is formatting the request is causing it to fail.
598   #
599   # NOTE: the error turned out to be something else completely!
600   def test_josm_upload
601     basic_authorization(users(:normal_user).email, "test")
602
603     # create a temporary changeset
604     content "<osm><changeset>" +
605       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
606       "</changeset></osm>"
607     put :create
608     assert_response :success
609     changeset_id = @response.body.to_i
610
611     diff = <<OSM
612 <osmChange version="0.6" generator="JOSM">
613 <create version="0.6" generator="JOSM">
614   <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
615   <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
616   <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
617   <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
618   <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
619   <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
620   <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
621   <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
622   <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
623   <way id='-10' action='modiy' visible='true' changeset='#{changeset_id}'>
624     <nd ref='-1' />
625     <nd ref='-2' />
626     <nd ref='-3' />
627     <nd ref='-4' />
628     <nd ref='-5' />
629     <nd ref='-6' />
630     <nd ref='-7' />
631     <nd ref='-8' />
632     <nd ref='-9' />
633     <tag k='highway' v='residential' />
634     <tag k='name' v='Foobar Street' />
635   </way>
636 </create>
637 </osmChange>
638 OSM
639
640     # upload it
641     content diff
642     post :upload, :id => changeset_id
643     assert_response :success, 
644       "can't upload a diff from JOSM: #{@response.body}"
645     
646     get :download, :id => changeset_id
647     assert_response :success
648
649     assert_select "osmChange", 1
650     assert_select "osmChange>create>node", 9
651     assert_select "osmChange>create>way", 1
652     assert_select "osmChange>create>way>nd", 9
653     assert_select "osmChange>create>way>tag", 2
654   end
655
656   ##
657   # when we make some complex changes we get the same changes back from the 
658   # diff download.
659   def test_diff_download_complex
660     basic_authorization(users(:normal_user).email, "test")
661
662     # create a temporary changeset
663     content "<osm><changeset>" +
664       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
665       "</changeset></osm>"
666     put :create
667     assert_response :success
668     changeset_id = @response.body.to_i
669
670     # add a diff to it
671     diff = <<EOF
672 <osmChange>
673  <delete>
674   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
675  </delete>
676  <create>
677   <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
678   <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
679   <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
680  </create>
681  <modify>
682   <node id='3' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
683   <way id='1' changeset='#{changeset_id}' version='1'>
684    <nd ref='3'/>
685    <nd ref='-1'/>
686    <nd ref='-2'/>
687    <nd ref='-3'/>
688   </way>
689  </modify>
690 </osmChange>
691 EOF
692
693     # upload it
694     content diff
695     post :upload, :id => changeset_id
696     assert_response :success, 
697       "can't upload multiple versions of an element in a diff: #{@response.body}"
698     
699     get :download, :id => changeset_id
700     assert_response :success
701
702     assert_select "osmChange", 1
703     assert_select "osmChange>create", 3
704     assert_select "osmChange>delete", 1
705     assert_select "osmChange>modify", 2
706     assert_select "osmChange>create>node", 3
707     assert_select "osmChange>delete>node", 1 
708     assert_select "osmChange>modify>node", 1
709     assert_select "osmChange>modify>way", 1
710   end
711
712   ##
713   # check that the bounding box of a changeset gets updated correctly
714   def test_changeset_bbox
715     basic_authorization "test@openstreetmap.org", "test"
716
717     # create a new changeset
718     content "<osm><changeset/></osm>"
719     put :create
720     assert_response :success, "Creating of changeset failed."
721     changeset_id = @response.body.to_i
722     
723     # add a single node to it
724     with_controller(NodeController.new) do
725       content "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
726       put :create
727       assert_response :success, "Couldn't create node."
728     end
729
730     # get the bounding box back from the changeset
731     get :read, :id => changeset_id
732     assert_response :success, "Couldn't read back changeset."
733     assert_select "osm>changeset[min_lon=1.0]", 1
734     assert_select "osm>changeset[max_lon=1.0]", 1
735     assert_select "osm>changeset[min_lat=2.0]", 1
736     assert_select "osm>changeset[max_lat=2.0]", 1
737
738     # add another node to it
739     with_controller(NodeController.new) do
740       content "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
741       put :create
742       assert_response :success, "Couldn't create second node."
743     end
744
745     # get the bounding box back from the changeset
746     get :read, :id => changeset_id
747     assert_response :success, "Couldn't read back changeset for the second time."
748     assert_select "osm>changeset[min_lon=1.0]", 1
749     assert_select "osm>changeset[max_lon=2.0]", 1
750     assert_select "osm>changeset[min_lat=1.0]", 1
751     assert_select "osm>changeset[max_lat=2.0]", 1
752
753     # add (delete) a way to it
754     with_controller(WayController.new) do
755       content update_changeset(current_ways(:visible_way).to_xml,
756                                changeset_id)
757       put :delete, :id => current_ways(:visible_way).id
758       assert_response :success, "Couldn't delete a way."
759     end
760
761     # get the bounding box back from the changeset
762     get :read, :id => changeset_id
763     assert_response :success, "Couldn't read back changeset for the third time."
764     assert_select "osm>changeset[min_lon=1.0]", 1
765     assert_select "osm>changeset[max_lon=3.1]", 1
766     assert_select "osm>changeset[min_lat=1.0]", 1
767     assert_select "osm>changeset[max_lat=3.1]", 1    
768   end
769
770   ##
771   # test that the changeset :include method works as it should
772   def test_changeset_include
773     basic_authorization "test@openstreetmap.org", "test"
774
775     # create a new changeset
776     content "<osm><changeset/></osm>"
777     put :create
778     assert_response :success, "Creating of changeset failed."
779     changeset_id = @response.body.to_i
780
781     # NOTE: the include method doesn't over-expand, like inserting
782     # a real method does. this is because we expect the client to 
783     # know what it is doing!
784     check_after_include(changeset_id,  1,  1, [ 1,  1,  1,  1])
785     check_after_include(changeset_id,  3,  3, [ 1,  1,  3,  3])
786     check_after_include(changeset_id,  4,  2, [ 1,  1,  4,  3])
787     check_after_include(changeset_id,  2,  2, [ 1,  1,  4,  3])
788     check_after_include(changeset_id, -1, -1, [-1, -1,  4,  3])
789     check_after_include(changeset_id, -2,  5, [-2, -1,  4,  5])
790   end
791
792   ##
793   # test the query functionality of changesets
794   def test_query
795     get :query, :bbox => "-10,-10, 10, 10"
796     assert_response :success, "can't get changesets in bbox"
797     assert_changesets [1,4,6]
798
799     get :query, :bbox => "4.5,4.5,4.6,4.6"
800     assert_response :success, "can't get changesets in bbox"
801     assert_changesets [1]
802
803     # can't get changesets of user 1 without authenticating
804     get :query, :user => users(:normal_user).id
805     assert_response :not_found, "shouldn't be able to get changesets by non-public user"
806
807     # but this should work
808     basic_authorization "test@openstreetmap.org", "test"
809     get :query, :user => users(:normal_user).id
810     assert_response :success, "can't get changesets by user"
811     assert_changesets [1,3,4,6]
812
813     get :query, :user => users(:normal_user).id, :open => true
814     assert_response :success, "can't get changesets by user and open"
815     assert_changesets [1,4]
816
817     get :query, :time => '2007-12-31'
818     assert_response :success, "can't get changesets by time-since"
819     assert_changesets [1,2,4,5,6]
820
821     get :query, :time => '2008-01-01T12:34Z'
822     assert_response :success, "can't get changesets by time-since with hour"
823     assert_changesets [1,2,4,5,6]
824
825     get :query, :time => '2007-12-31T23:59Z,2008-01-01T00:01Z'
826     assert_response :success, "can't get changesets by time-range"
827     assert_changesets [1,4,5,6]
828
829     get :query, :open => 'true'
830     assert_response :success, "can't get changesets by open-ness"
831     assert_changesets [1,2,4]
832   end
833
834   ##
835   # check that errors are returned if garbage is inserted 
836   # into query strings
837   def test_query_invalid
838     [ "abracadabra!",
839       "1,2,3,F",
840       ";drop table users;"
841       ].each do |bbox|
842       get :query, :bbox => bbox
843       assert_response :bad_request, "'#{bbox}' isn't a bbox"
844     end
845
846     [ "now()",
847       "00-00-00",
848       ";drop table users;",
849       ",",
850       "-,-"
851       ].each do |time|
852       get :query, :time => time
853       assert_response :bad_request, "'#{time}' isn't a valid time range"
854     end
855
856     [ "me",
857       "foobar",
858       "-1",
859       "0"
860       ].each do |uid|
861       get :query, :user => uid
862       assert_response :bad_request, "'#{uid}' isn't a valid user ID"
863     end
864   end
865
866   ##
867   # check updating tags on a changeset
868   def test_changeset_update
869     changeset = changesets(:normal_user_first_change)
870     new_changeset = changeset.to_xml
871     new_tag = XML::Node.new "tag"
872     new_tag['k'] = "tagtesting"
873     new_tag['v'] = "valuetesting"
874     new_changeset.find("//osm/changeset").first << new_tag
875     content new_changeset
876
877     # try without any authorization
878     put :update, :id => changeset.id
879     assert_response :unauthorized
880
881     # try with the wrong authorization
882     basic_authorization "test@example.com", "test"
883     put :update, :id => changeset.id
884     assert_response :conflict
885
886     # now this should work...
887     basic_authorization "test@openstreetmap.org", "test"
888     put :update, :id => changeset.id
889     assert_response :success
890
891     assert_select "osm>changeset[id=#{changeset.id}]", 1
892     assert_select "osm>changeset>tag", 2
893     assert_select "osm>changeset>tag[k=tagtesting][v=valuetesting]", 1
894   end
895   
896   ##
897   # check that a user different from the one who opened the changeset
898   # can't modify it.
899   def test_changeset_update_invalid
900     basic_authorization "test@example.com", "test"
901
902     changeset = changesets(:normal_user_first_change)
903     new_changeset = changeset.to_xml
904     new_tag = XML::Node.new "tag"
905     new_tag['k'] = "testing"
906     new_tag['v'] = "testing"
907     new_changeset.find("//osm/changeset").first << new_tag
908
909     content new_changeset
910     put :update, :id => changeset.id
911     assert_response :conflict
912   end
913
914   ##
915   # check that a changeset can contain a certain max number of changes.
916   def test_changeset_limits
917     basic_authorization "test@openstreetmap.org", "test"
918
919     # open a new changeset
920     content "<osm><changeset/></osm>"
921     put :create
922     assert_response :success, "can't create a new changeset"
923     cs_id = @response.body.to_i
924
925     # start the counter just short of where the changeset should finish.
926     offset = 10
927     # alter the database to set the counter on the changeset directly, 
928     # otherwise it takes about 6 minutes to fill all of them.
929     changeset = Changeset.find(cs_id)
930     changeset.num_changes = Changeset::MAX_ELEMENTS - offset
931     changeset.save!
932
933     with_controller(NodeController.new) do
934       # create a new node
935       content "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
936       put :create
937       assert_response :success, "can't create a new node"
938       node_id = @response.body.to_i
939
940       get :read, :id => node_id
941       assert_response :success, "can't read back new node"
942       node_doc = XML::Parser.string(@response.body).parse
943       node_xml = node_doc.find("//osm/node").first
944
945       # loop until we fill the changeset with nodes
946       offset.times do |i|
947         node_xml['lat'] = rand.to_s
948         node_xml['lon'] = rand.to_s
949         node_xml['version'] = (i+1).to_s
950
951         content node_doc
952         put :update, :id => node_id
953         assert_response :success, "attempt #{i} should have succeeded"
954       end
955
956       # trying again should fail
957       node_xml['lat'] = rand.to_s
958       node_xml['lon'] = rand.to_s
959       node_xml['version'] = offset.to_s
960       
961       content node_doc
962       put :update, :id => node_id
963       assert_response :conflict, "final attempt should have failed"
964     end
965
966     changeset = Changeset.find(cs_id)
967     assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
968
969     # check that the changeset is now closed as well
970     assert(!changeset.is_open?, 
971            "changeset should have been auto-closed by exceeding " + 
972            "element limit.")
973   end
974   
975   #------------------------------------------------------------
976   # utility functions
977   #------------------------------------------------------------
978
979   ##
980   # boilerplate for checking that certain changesets exist in the
981   # output.
982   def assert_changesets(ids)
983     assert_select "osm>changeset", ids.size
984     ids.each do |id|
985       assert_select "osm>changeset[id=#{id}]", 1
986     end
987   end
988
989   ##
990   # call the include method and assert properties of the bbox
991   def check_after_include(changeset_id, lon, lat, bbox)
992     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
993     post :expand_bbox, :id => changeset_id
994     assert_response :success, "Setting include of changeset failed: #{@response.body}"
995
996     # check exactly one changeset
997     assert_select "osm>changeset", 1
998     assert_select "osm>changeset[id=#{changeset_id}]", 1
999
1000     # check the bbox
1001     doc = XML::Parser.string(@response.body).parse
1002     changeset = doc.find("//osm/changeset").first
1003     assert_equal bbox[0], changeset['min_lon'].to_f, "min lon"
1004     assert_equal bbox[1], changeset['min_lat'].to_f, "min lat"
1005     assert_equal bbox[2], changeset['max_lon'].to_f, "max lon"
1006     assert_equal bbox[3], changeset['max_lat'].to_f, "max lat"
1007   end
1008
1009   ##
1010   # update the changeset_id of a way element
1011   def update_changeset(xml, changeset_id)
1012     xml_attr_rewrite(xml, 'changeset', changeset_id)
1013   end
1014
1015   ##
1016   # update an attribute in a way element
1017   def xml_attr_rewrite(xml, name, value)
1018     xml.find("//osm/way").first[name] = value.to_s
1019     return xml
1020   end
1021
1022 end