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