]> git.openstreetmap.org Git - rails.git/blob - test/functional/changeset_controller_test.rb
better handling of duplicate tags. Extra validation in the tests.
[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     put :close, :id => changesets(:normal_user_first_change).id
69     assert_response :success
70   end
71
72   ##
73   # test that a different user can't close another user's changeset
74   def test_close_invalid
75     basic_authorization "test@example.com", "test"
76
77     put :close, :id => changesets(:normal_user_first_change).id
78     assert_response :conflict
79     assert_equal "The user doesn't own that changeset", @response.body
80   end
81
82   ##
83   # upload something simple, but valid and check that it can 
84   # be read back ok.
85   def test_upload_simple_valid
86     basic_authorization "test@openstreetmap.org", "test"
87
88     # simple diff to change a node, way and relation by removing 
89     # their tags
90     diff = <<EOF
91 <osmChange>
92  <modify>
93   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
94   <way id='1' changeset='1' version='1'>
95    <nd ref='3'/>
96   </way>
97  </modify>
98  <modify>
99   <relation id='1' changeset='1' version='1'>
100    <member type='way' role='some' ref='3'/>
101    <member type='node' role='some' ref='5'/>
102    <member type='relation' role='some' ref='3'/>
103   </relation>
104  </modify>
105 </osmChange>
106 EOF
107
108     # upload it
109     content diff
110     post :upload, :id => 1
111     assert_response :success, 
112       "can't upload a simple valid diff to changeset: #{@response.body}"
113
114     # check that the changes made it into the database
115     assert_equal 0, Node.find(1).tags.size, "node 1 should now have no tags"
116     assert_equal 0, Way.find(1).tags.size, "way 1 should now have no tags"
117     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
118   end
119     
120   ##
121   # upload something which creates new objects using placeholders
122   def test_upload_create_valid
123     basic_authorization "test@openstreetmap.org", "test"
124
125     # simple diff to create a node way and relation using placeholders
126     diff = <<EOF
127 <osmChange>
128  <create>
129   <node id='-1' lon='0' lat='0' changeset='1'>
130    <tag k='foo' v='bar'/>
131    <tag k='baz' v='bat'/>
132   </node>
133   <way id='-1' changeset='1'>
134    <nd ref='3'/>
135   </way>
136  </create>
137  <create>
138   <relation id='-1' changeset='1'>
139    <member type='way' role='some' ref='3'/>
140    <member type='node' role='some' ref='5'/>
141    <member type='relation' role='some' ref='3'/>
142   </relation>
143  </create>
144 </osmChange>
145 EOF
146
147     # upload it
148     content diff
149     post :upload, :id => 1
150     assert_response :success, 
151       "can't upload a simple valid creation to changeset: #{@response.body}"
152
153     # check the returned payload
154     assert_select "diffResult[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
155     assert_select "diffResult>node", 1
156     assert_select "diffresult>way", 1
157     assert_select "diffResult>relation", 1
158
159     # inspect the response to find out what the new element IDs are
160     doc = XML::Parser.string(@response.body).parse
161     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
162     new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
163     new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
164
165     # check the old IDs are all present and negative one
166     assert_equal -1, doc.find("//diffResult/node").first["old_id"].to_i
167     assert_equal -1, doc.find("//diffResult/way").first["old_id"].to_i
168     assert_equal -1, doc.find("//diffResult/relation").first["old_id"].to_i
169
170     # check the versions are present and equal one
171     assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
172     assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
173     assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
174
175     # check that the changes made it into the database
176     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
177     assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
178     assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
179   end
180     
181   ##
182   # test a complex delete where we delete elements which rely on eachother
183   # in the same transaction.
184   def test_upload_delete
185     basic_authorization "test@openstreetmap.org", "test"
186
187     diff = XML::Document.new
188     diff.root = XML::Node.new "osmChange"
189     delete = XML::Node.new "delete"
190     diff.root << delete
191     delete << current_relations(:visible_relation).to_xml_node
192     delete << current_relations(:used_relation).to_xml_node
193     delete << current_ways(:used_way).to_xml_node
194     delete << current_nodes(:node_used_by_relationship).to_xml_node
195
196     # upload it
197     content diff
198     post :upload, :id => 1
199     assert_response :success, 
200       "can't upload a deletion diff to changeset: #{@response.body}"
201
202     # check that everything was deleted
203     assert_equal false, Node.find(current_nodes(:node_used_by_relationship).id).visible
204     assert_equal false, Way.find(current_ways(:used_way).id).visible
205     assert_equal false, Relation.find(current_relations(:visible_relation).id).visible
206     assert_equal false, Relation.find(current_relations(:used_relation).id).visible
207   end
208
209   ##
210   # test that deleting stuff in a transaction doesn't bypass the checks
211   # to ensure that used elements are not deleted.
212   def test_upload_delete_invalid
213     basic_authorization "test@openstreetmap.org", "test"
214
215     diff = XML::Document.new
216     diff.root = XML::Node.new "osmChange"
217     delete = XML::Node.new "delete"
218     diff.root << delete
219     delete << current_relations(:visible_relation).to_xml_node
220     delete << current_ways(:used_way).to_xml_node
221     delete << current_nodes(:node_used_by_relationship).to_xml_node
222
223     # upload it
224     content diff
225     post :upload, :id => 1
226     assert_response :precondition_failed, 
227       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
228
229     # check that nothing was, in fact, deleted
230     assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
231     assert_equal true, Way.find(current_ways(:used_way).id).visible
232     assert_equal true, Relation.find(current_relations(:visible_relation).id).visible
233   end
234
235   ##
236   # upload something which creates new objects and inserts them into
237   # existing containers using placeholders.
238   def test_upload_complex
239     basic_authorization "test@openstreetmap.org", "test"
240
241     # simple diff to create a node way and relation using placeholders
242     diff = <<EOF
243 <osmChange>
244  <create>
245   <node id='-1' lon='0' lat='0' changeset='1'>
246    <tag k='foo' v='bar'/>
247    <tag k='baz' v='bat'/>
248   </node>
249  </create>
250  <modify>
251   <way id='1' changeset='1' version='1'>
252    <nd ref='-1'/>
253    <nd ref='3'/>
254   </way>
255   <relation id='1' changeset='1' version='1'>
256    <member type='way' role='some' ref='3'/>
257    <member type='node' role='some' ref='-1'/>
258    <member type='relation' role='some' ref='3'/>
259   </relation>
260  </modify>
261 </osmChange>
262 EOF
263
264     # upload it
265     content diff
266     post :upload, :id => 1
267     assert_response :success, 
268       "can't upload a complex diff to changeset: #{@response.body}"
269
270     # check the returned payload
271     assert_select "diffResult[version=#{API_VERSION}][generator=\"#{GENERATOR}\"]", 1
272     assert_select "diffResult>node", 1
273     assert_select "diffResult>way", 1
274     assert_select "diffResult>relation", 1
275
276     # inspect the response to find out what the new element IDs are
277     doc = XML::Parser.string(@response.body).parse
278     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
279
280     # check that the changes made it into the database
281     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
282     assert_equal [new_node_id, 3], Way.find(1).nds, "way nodes should match"
283     Relation.find(1).members.each do |type,id,role|
284       if type == 'node'
285         assert_equal new_node_id, id, "relation should contain new node"
286       end
287     end
288   end
289     
290   ##
291   # create a diff which references several changesets, which should cause
292   # a rollback and none of the diff gets committed
293   def test_upload_invalid_changesets
294     basic_authorization "test@openstreetmap.org", "test"
295
296     # simple diff to create a node way and relation using placeholders
297     diff = <<EOF
298 <osmChange>
299  <modify>
300   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
301   <way id='1' changeset='1' version='1'>
302    <nd ref='3'/>
303   </way>
304  </modify>
305  <modify>
306   <relation id='1' changeset='1' version='1'>
307    <member type='way' role='some' ref='3'/>
308    <member type='node' role='some' ref='5'/>
309    <member type='relation' role='some' ref='3'/>
310   </relation>
311  </modify>
312  <create>
313   <node id='-1' changeset='4'>
314    <tag k='foo' v='bar'/>
315    <tag k='baz' v='bat'/>
316   </node>
317  </create>
318 </osmChange>
319 EOF
320     # cache the objects before uploading them
321     node = current_nodes(:visible_node)
322     way = current_ways(:visible_way)
323     rel = current_relations(:visible_relation)
324
325     # upload it
326     content diff
327     post :upload, :id => 1
328     assert_response :conflict, 
329       "uploading a diff with multiple changsets should have failed"
330
331     # check that objects are unmodified
332     assert_nodes_are_equal(node, Node.find(1))
333     assert_ways_are_equal(way, Way.find(1))
334   end
335     
336   ##
337   # upload multiple versions of the same element in the same diff.
338   def test_upload_multiple_valid
339     basic_authorization "test@openstreetmap.org", "test"
340
341     # change the location of a node multiple times, each time referencing
342     # the last version. doesn't this depend on version numbers being
343     # sequential?
344     diff = <<EOF
345 <osmChange>
346  <modify>
347   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
348   <node id='1' lon='1' lat='0' changeset='1' version='2'/>
349   <node id='1' lon='1' lat='1' changeset='1' version='3'/>
350   <node id='1' lon='1' lat='2' changeset='1' version='4'/>
351   <node id='1' lon='2' lat='2' changeset='1' version='5'/>
352   <node id='1' lon='3' lat='2' changeset='1' version='6'/>
353   <node id='1' lon='3' lat='3' changeset='1' version='7'/>
354   <node id='1' lon='9' lat='9' changeset='1' version='8'/>
355  </modify>
356 </osmChange>
357 EOF
358
359     # upload it
360     content diff
361     post :upload, :id => 1
362     assert_response :success, 
363       "can't upload multiple versions of an element in a diff: #{@response.body}"
364   end
365
366   ##
367   # upload multiple versions of the same element in the same diff, but
368   # keep the version numbers the same.
369   def test_upload_multiple_duplicate
370     basic_authorization "test@openstreetmap.org", "test"
371
372     diff = <<EOF
373 <osmChange>
374  <modify>
375   <node id='1' lon='0' lat='0' changeset='1' version='1'/>
376   <node id='1' lon='1' lat='1' changeset='1' version='1'/>
377  </modify>
378 </osmChange>
379 EOF
380
381     # upload it
382     content diff
383     post :upload, :id => 1
384     assert_response :conflict, 
385       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
386   end
387
388   ##
389   # try to upload some elements without specifying the version
390   def test_upload_missing_version
391     basic_authorization "test@openstreetmap.org", "test"
392
393     diff = <<EOF
394 <osmChange>
395  <modify>
396   <node id='1' lon='1' lat='1' changeset='1'/>
397  </modify>
398 </osmChange>
399 EOF
400
401     # upload it
402     content diff
403     post :upload, :id => 1
404     assert_response :bad_request, 
405       "shouldn't be able to upload an element without version: #{@response.body}"
406   end
407   
408   ##
409   # try to upload with commands other than create, modify, or delete
410   def test_action_upload_invalid
411     basic_authorization "test@openstreetmap.org", "test"
412     
413     diff = <<EOF
414 <osmChange>
415   <ping>
416     <node id='1' lon='1' lat='1' changeset='1' />
417   </ping>
418 </osmChange>
419 EOF
420   content diff
421   post :upload, :id => 1
422   assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
423   assert_equal @response.body, "Unknown action ping, choices are create, modify, delete."
424   end
425
426   ##
427   # when we make some simple changes we get the same changes back from the 
428   # diff download.
429   def test_diff_download_simple
430     basic_authorization(users(:normal_user).email, "test")
431
432     # create a temporary changeset
433     content "<osm><changeset>" +
434       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
435       "</changeset></osm>"
436     put :create
437     assert_response :success
438     changeset_id = @response.body.to_i
439
440     # add a diff to it
441     diff = <<EOF
442 <osmChange>
443  <modify>
444   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
445   <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
446   <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
447   <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
448   <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
449   <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
450   <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
451   <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
452  </modify>
453 </osmChange>
454 EOF
455
456     # upload it
457     content diff
458     post :upload, :id => changeset_id
459     assert_response :success, 
460       "can't upload multiple versions of an element in a diff: #{@response.body}"
461     
462     get :download, :id => changeset_id
463     assert_response :success
464
465     assert_select "osmChange", 1
466     assert_select "osmChange>modify", 8
467     assert_select "osmChange>modify>node", 8
468   end
469   
470   ##
471   # culled this from josm to ensure that nothing in the way that josm
472   # is formatting the request is causing it to fail.
473   #
474   # NOTE: the error turned out to be something else completely!
475   def test_josm_upload
476     basic_authorization(users(:normal_user).email, "test")
477
478     # create a temporary changeset
479     content "<osm><changeset>" +
480       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
481       "</changeset></osm>"
482     put :create
483     assert_response :success
484     changeset_id = @response.body.to_i
485
486     diff = <<OSM
487 <osmChange version="0.6" generator="JOSM">
488 <create version="0.6" generator="JOSM">
489   <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
490   <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
491   <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
492   <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
493   <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
494   <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
495   <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
496   <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
497   <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
498   <way id='-10' action='modiy' visible='true' changeset='#{changeset_id}'>
499     <nd ref='-1' />
500     <nd ref='-2' />
501     <nd ref='-3' />
502     <nd ref='-4' />
503     <nd ref='-5' />
504     <nd ref='-6' />
505     <nd ref='-7' />
506     <nd ref='-8' />
507     <nd ref='-9' />
508     <tag k='highway' v='residential' />
509     <tag k='name' v='Foobar Street' />
510   </way>
511 </create>
512 </osmChange>
513 OSM
514
515     # upload it
516     content diff
517     post :upload, :id => changeset_id
518     assert_response :success, 
519       "can't upload a diff from JOSM: #{@response.body}"
520     
521     get :download, :id => changeset_id
522     assert_response :success
523
524     assert_select "osmChange", 1
525     assert_select "osmChange>create>node", 9
526     assert_select "osmChange>create>way", 1
527     assert_select "osmChange>create>way>nd", 9
528     assert_select "osmChange>create>way>tag", 2
529   end
530
531   ##
532   # when we make some complex changes we get the same changes back from the 
533   # diff download.
534   def test_diff_download_complex
535     basic_authorization(users(:normal_user).email, "test")
536
537     # create a temporary changeset
538     content "<osm><changeset>" +
539       "<tag k='created_by' v='osm test suite checking changesets'/>" + 
540       "</changeset></osm>"
541     put :create
542     assert_response :success
543     changeset_id = @response.body.to_i
544
545     # add a diff to it
546     diff = <<EOF
547 <osmChange>
548  <delete>
549   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
550  </delete>
551  <create>
552   <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
553   <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
554   <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
555  </create>
556  <modify>
557   <node id='3' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
558   <way id='1' changeset='#{changeset_id}' version='1'>
559    <nd ref='3'/>
560    <nd ref='-1'/>
561    <nd ref='-2'/>
562    <nd ref='-3'/>
563   </way>
564  </modify>
565 </osmChange>
566 EOF
567
568     # upload it
569     content diff
570     post :upload, :id => changeset_id
571     assert_response :success, 
572       "can't upload multiple versions of an element in a diff: #{@response.body}"
573     
574     get :download, :id => changeset_id
575     assert_response :success
576
577     assert_select "osmChange", 1
578     assert_select "osmChange>create", 3
579     assert_select "osmChange>delete", 1
580     assert_select "osmChange>modify", 2
581     assert_select "osmChange>create>node", 3
582     assert_select "osmChange>delete>node", 1 
583     assert_select "osmChange>modify>node", 1
584     assert_select "osmChange>modify>way", 1
585   end
586
587   ##
588   # check that the bounding box of a changeset gets updated correctly
589   def test_changeset_bbox
590     basic_authorization "test@openstreetmap.org", "test"
591
592     # create a new changeset
593     content "<osm><changeset/></osm>"
594     put :create
595     assert_response :success, "Creating of changeset failed."
596     changeset_id = @response.body.to_i
597     
598     # add a single node to it
599     with_controller(NodeController.new) do
600       content "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
601       put :create
602       assert_response :success, "Couldn't create node."
603     end
604
605     # get the bounding box back from the changeset
606     get :read, :id => changeset_id
607     assert_response :success, "Couldn't read back changeset."
608     assert_select "osm>changeset[min_lon=1.0]", 1
609     assert_select "osm>changeset[max_lon=1.0]", 1
610     assert_select "osm>changeset[min_lat=2.0]", 1
611     assert_select "osm>changeset[max_lat=2.0]", 1
612
613     # add another node to it
614     with_controller(NodeController.new) do
615       content "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
616       put :create
617       assert_response :success, "Couldn't create second node."
618     end
619
620     # get the bounding box back from the changeset
621     get :read, :id => changeset_id
622     assert_response :success, "Couldn't read back changeset for the second time."
623     assert_select "osm>changeset[min_lon=1.0]", 1
624     assert_select "osm>changeset[max_lon=2.0]", 1
625     assert_select "osm>changeset[min_lat=1.0]", 1
626     assert_select "osm>changeset[max_lat=2.0]", 1
627
628     # add (delete) a way to it
629     with_controller(WayController.new) do
630       content update_changeset(current_ways(:visible_way).to_xml,
631                                changeset_id)
632       put :delete, :id => current_ways(:visible_way).id
633       assert_response :success, "Couldn't delete a way."
634     end
635
636     # get the bounding box back from the changeset
637     get :read, :id => changeset_id
638     assert_response :success, "Couldn't read back changeset for the third time."
639     assert_select "osm>changeset[min_lon=1.0]", 1
640     assert_select "osm>changeset[max_lon=3.1]", 1
641     assert_select "osm>changeset[min_lat=1.0]", 1
642     assert_select "osm>changeset[max_lat=3.1]", 1    
643   end
644
645   ##
646   # test that the changeset :include method works as it should
647   def test_changeset_include
648     basic_authorization "test@openstreetmap.org", "test"
649
650     # create a new changeset
651     content "<osm><changeset/></osm>"
652     put :create
653     assert_response :success, "Creating of changeset failed."
654     changeset_id = @response.body.to_i
655
656     # NOTE: the include method doesn't over-expand, like inserting
657     # a real method does. this is because we expect the client to 
658     # know what it is doing!
659     check_after_include(changeset_id,  1,  1, [ 1,  1,  1,  1])
660     check_after_include(changeset_id,  3,  3, [ 1,  1,  3,  3])
661     check_after_include(changeset_id,  4,  2, [ 1,  1,  4,  3])
662     check_after_include(changeset_id,  2,  2, [ 1,  1,  4,  3])
663     check_after_include(changeset_id, -1, -1, [-1, -1,  4,  3])
664     check_after_include(changeset_id, -2,  5, [-2, -1,  4,  5])
665   end
666
667   ##
668   # test the query functionality of changesets
669   def test_query
670     get :query, :bbox => "-10,-10, 10, 10"
671     assert_response :success, "can't get changesets in bbox"
672     assert_changesets [1,4]
673
674     get :query, :bbox => "4.5,4.5,4.6,4.6"
675     assert_response :success, "can't get changesets in bbox"
676     assert_changesets [1]
677
678     # can't get changesets of user 1 without authenticating
679     get :query, :user => users(:normal_user).id
680     assert_response :not_found, "shouldn't be able to get changesets by non-public user"
681
682     # but this should work
683     basic_authorization "test@openstreetmap.org", "test"
684     get :query, :user => users(:normal_user).id
685     assert_response :success, "can't get changesets by user"
686     assert_changesets [1,3,4]
687
688     get :query, :user => users(:normal_user).id, :open => true
689     assert_response :success, "can't get changesets by user and open"
690     assert_changesets [1,4]
691
692     get :query, :time => '2007-12-31'
693     assert_response :success, "can't get changesets by time-since"
694     assert_changesets [1,2,4,5]
695
696     get :query, :time => '2008-01-01T12:34Z'
697     assert_response :success, "can't get changesets by time-since with hour"
698     assert_changesets [1,2,4,5]
699
700     get :query, :time => '2007-12-31T23:59Z,2008-01-01T00:01Z'
701     assert_response :success, "can't get changesets by time-range"
702     assert_changesets [1,4,5]
703
704     get :query, :open => 'true'
705     assert_response :success, "can't get changesets by open-ness"
706     assert_changesets [1,2,4]
707   end
708
709   ##
710   # check that errors are returned if garbage is inserted 
711   # into query strings
712   def test_query_invalid
713     [ "abracadabra!",
714       "1,2,3,F",
715       ";drop table users;"
716       ].each do |bbox|
717       get :query, :bbox => bbox
718       assert_response :bad_request, "'#{bbox}' isn't a bbox"
719     end
720
721     [ "now()",
722       "00-00-00",
723       ";drop table users;",
724       ",",
725       "-,-"
726       ].each do |time|
727       get :query, :time => time
728       assert_response :bad_request, "'#{time}' isn't a valid time range"
729     end
730
731     [ "me",
732       "foobar",
733       "-1",
734       "0"
735       ].each do |uid|
736       get :query, :user => uid
737       assert_response :bad_request, "'#{uid}' isn't a valid user ID"
738     end
739   end
740
741   ##
742   # check updating tags on a changeset
743   def test_changeset_update
744     changeset = changesets(:normal_user_first_change)
745     new_changeset = changeset.to_xml
746     new_tag = XML::Node.new "tag"
747     new_tag['k'] = "tagtesting"
748     new_tag['v'] = "valuetesting"
749     new_changeset.find("//osm/changeset").first << new_tag
750     content new_changeset
751
752     # try without any authorization
753     put :update, :id => changeset.id
754     assert_response :unauthorized
755
756     # try with the wrong authorization
757     basic_authorization "test@example.com", "test"
758     put :update, :id => changeset.id
759     assert_response :conflict
760
761     # now this should work...
762     basic_authorization "test@openstreetmap.org", "test"
763     put :update, :id => changeset.id
764     assert_response :success
765
766     assert_select "osm>changeset[id=#{changeset.id}]", 1
767     assert_select "osm>changeset>tag", 2
768     assert_select "osm>changeset>tag[k=tagtesting][v=valuetesting]", 1
769   end
770   
771   ##
772   # check that a user different from the one who opened the changeset
773   # can't modify it.
774   def test_changeset_update_invalid
775     basic_authorization "test@example.com", "test"
776
777     changeset = changesets(:normal_user_first_change)
778     new_changeset = changeset.to_xml
779     new_tag = XML::Node.new "tag"
780     new_tag['k'] = "testing"
781     new_tag['v'] = "testing"
782     new_changeset.find("//osm/changeset").first << new_tag
783
784     content new_changeset
785     put :update, :id => changeset.id
786     assert_response :conflict
787   end
788
789   ##
790   # check that a changeset can contain a certain max number of changes.
791   def test_changeset_limits
792     basic_authorization "test@openstreetmap.org", "test"
793
794     # open a new changeset
795     content "<osm><changeset/></osm>"
796     put :create
797     assert_response :success, "can't create a new changeset"
798     cs_id = @response.body.to_i
799
800     # start the counter just short of where the changeset should finish.
801     offset = 10
802     # alter the database to set the counter on the changeset directly, 
803     # otherwise it takes about 6 minutes to fill all of them.
804     changeset = Changeset.find(cs_id)
805     changeset.num_changes = Changeset::MAX_ELEMENTS - offset
806     changeset.save!
807
808     with_controller(NodeController.new) do
809       # create a new node
810       content "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
811       put :create
812       assert_response :success, "can't create a new node"
813       node_id = @response.body.to_i
814
815       get :read, :id => node_id
816       assert_response :success, "can't read back new node"
817       node_doc = XML::Parser.string(@response.body).parse
818       node_xml = node_doc.find("//osm/node").first
819
820       # loop until we fill the changeset with nodes
821       offset.times do |i|
822         node_xml['lat'] = rand.to_s
823         node_xml['lon'] = rand.to_s
824         node_xml['version'] = (i+1).to_s
825
826         content node_doc
827         put :update, :id => node_id
828         assert_response :success, "attempt #{i} should have succeeded"
829       end
830
831       # trying again should fail
832       node_xml['lat'] = rand.to_s
833       node_xml['lon'] = rand.to_s
834       node_xml['version'] = offset.to_s
835       
836       content node_doc
837       put :update, :id => node_id
838       assert_response :conflict, "final attempt should have failed"
839     end
840
841     changeset = Changeset.find(cs_id)
842     assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
843   end
844   
845   #------------------------------------------------------------
846   # utility functions
847   #------------------------------------------------------------
848
849   ##
850   # boilerplate for checking that certain changesets exist in the
851   # output.
852   def assert_changesets(ids)
853     assert_select "osm>changeset", ids.size
854     ids.each do |id|
855       assert_select "osm>changeset[id=#{id}]", 1
856     end
857   end
858
859   ##
860   # call the include method and assert properties of the bbox
861   def check_after_include(changeset_id, lon, lat, bbox)
862     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
863     post :expand_bbox, :id => changeset_id
864     assert_response :success, "Setting include of changeset failed: #{@response.body}"
865
866     # check exactly one changeset
867     assert_select "osm>changeset", 1
868     assert_select "osm>changeset[id=#{changeset_id}]", 1
869
870     # check the bbox
871     doc = XML::Parser.string(@response.body).parse
872     changeset = doc.find("//osm/changeset").first
873     assert_equal bbox[0], changeset['min_lon'].to_f, "min lon"
874     assert_equal bbox[1], changeset['min_lat'].to_f, "min lat"
875     assert_equal bbox[2], changeset['max_lon'].to_f, "max lon"
876     assert_equal bbox[3], changeset['max_lat'].to_f, "max lat"
877   end
878
879   ##
880   # update the changeset_id of a way element
881   def update_changeset(xml, changeset_id)
882     xml_attr_rewrite(xml, 'changeset', changeset_id)
883   end
884
885   ##
886   # update an attribute in a way element
887   def xml_attr_rewrite(xml, name, value)
888     xml.find("//osm/way").first[name] = value.to_s
889     return xml
890   end
891
892 end