Merge pull request #14 from tomhughes/routing
[rails.git] / test / controllers / changeset_controller_test.rb
1 require 'test_helper'
2 require 'changeset_controller'
3
4 class ChangesetControllerTest < ActionController::TestCase
5   api_fixtures
6
7   ##
8   # test all routes which lead to this controller
9   def test_routes
10     assert_routing(
11       { :path => "/api/0.6/changeset/create", :method => :put },
12       { :controller => "changeset", :action => "create" }
13     )
14     assert_routing(
15       { :path => "/api/0.6/changeset/1/upload", :method => :post },
16       { :controller => "changeset", :action => "upload", :id => "1" }
17     )
18     assert_routing(
19       { :path => "/api/0.6/changeset/1/download", :method => :get },
20       { :controller => "changeset", :action => "download", :id => "1" }
21     )
22     assert_routing(
23       { :path => "/api/0.6/changeset/1/expand_bbox", :method => :post },
24       { :controller => "changeset", :action => "expand_bbox", :id => "1" }
25     )
26     assert_routing(
27       { :path => "/api/0.6/changeset/1", :method => :get },
28       { :controller => "changeset", :action => "read", :id => "1" }
29     )
30     assert_routing(
31       { :path => "/api/0.6/changeset/1", :method => :put },
32       { :controller => "changeset", :action => "update", :id => "1" }
33     )
34     assert_routing(
35       { :path => "/api/0.6/changeset/1/close", :method => :put },
36       { :controller => "changeset", :action => "close", :id => "1" }
37     )
38     assert_routing(
39       { :path => "/api/0.6/changesets", :method => :get },
40       { :controller => "changeset", :action => "query" }
41     )
42     assert_routing(
43       { :path => "/user/name/history", :method => :get },
44       { :controller => "changeset", :action => "list", :display_name => "name" }
45     )
46     assert_routing(
47       { :path => "/user/name/history/feed", :method => :get },
48       { :controller => "changeset", :action => "feed", :display_name => "name", :format => :atom }
49     )
50     assert_routing(
51       { :path => "/history/friends", :method => :get },
52       { :controller => "changeset", :action => "list", :friends => true }
53     )
54     assert_routing(
55       { :path => "/history/nearby", :method => :get },
56       { :controller => "changeset", :action => "list", :nearby => true }
57     )
58     assert_routing(
59       { :path => "/history", :method => :get },
60       { :controller => "changeset", :action => "list" }
61     )
62     assert_routing(
63       { :path => "/history/feed", :method => :get },
64       { :controller => "changeset", :action => "feed", :format => :atom }
65     )
66   end
67
68   # -----------------------
69   # Test simple changeset creation
70   # -----------------------
71
72   def test_create
73     basic_authorization users(:normal_user).email, "test"
74     # Create the first user's changeset
75     content "<osm><changeset>" +
76       "<tag k='created_by' v='osm test suite checking changesets'/>" +
77       "</changeset></osm>"
78     put :create
79     assert_require_public_data
80
81
82     basic_authorization users(:public_user).email, "test"
83     # Create the first user's changeset
84     content "<osm><changeset>" +
85       "<tag k='created_by' v='osm test suite checking changesets'/>" +
86       "</changeset></osm>"
87     put :create
88
89     assert_response :success, "Creation of changeset did not return sucess status"
90     newid = @response.body.to_i
91
92     # check end time, should be an hour ahead of creation time
93     cs = Changeset.find(newid)
94     duration = cs.closed_at - cs.created_at
95     # the difference can either be a rational, or a floating point number
96     # of seconds, depending on the code path taken :-(
97     if duration.class == Rational
98       assert_equal Rational(1,24), duration , "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
99     else
100       # must be number of seconds...
101       assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
102     end
103   end
104
105   def test_create_invalid
106     basic_authorization users(:normal_user).email, "test"
107     content "<osm><changeset></osm>"
108     put :create
109     assert_require_public_data
110
111     ## Try the public user
112     basic_authorization users(:public_user).email, "test"
113     content "<osm><changeset></osm>"
114     put :create
115     assert_response :bad_request, "creating a invalid changeset should fail"
116   end
117
118   def test_create_invalid_no_content
119     ## First check with no auth
120     put :create
121     assert_response :unauthorized, "shouldn't be able to create a changeset with no auth"
122
123     ## Now try to with the non-public user
124     basic_authorization users(:normal_user).email, "test"
125     put :create
126     assert_require_public_data
127
128     ## Try the inactive user
129     basic_authorization users(:inactive_user).email, "test"
130     put :create
131     assert_inactive_user
132
133     ## Now try to use the public user
134     basic_authorization users(:public_user).email, "test"
135     put :create
136     assert_response :bad_request, "creating a changeset with no content should fail"
137   end
138
139   def test_create_wrong_method
140     basic_authorization users(:public_user).email, "test"
141     get :create
142     assert_response :method_not_allowed
143     post :create
144     assert_response :method_not_allowed
145   end
146
147   ##
148   # check that the changeset can be read and returns the correct
149   # document structure.
150   def test_read
151     changeset_id = changesets(:normal_user_first_change).id
152     get :read, :id => changeset_id
153     assert_response :success, "cannot get first changeset"
154
155     assert_select "osm[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
156     assert_select "osm>changeset[id=#{changeset_id}]", 1
157   end
158
159   ##
160   # check that a changeset that doesn't exist returns an appropriate message
161   def test_read_not_found
162     [0, -32, 233455644, "afg", "213"].each do |id|
163       begin
164         get :read, :id => id
165         assert_response :not_found, "should get a not found"
166       rescue ActionController::UrlGenerationError => ex
167         assert_match /No route matches/, ex.to_s
168       end
169     end
170   end
171
172   ##
173   # test that the user who opened a change can close it
174   def test_close
175     ## Try without authentication
176     put :close, :id => changesets(:public_user_first_change).id
177     assert_response :unauthorized
178
179
180     ## Try using the non-public user
181     basic_authorization users(:normal_user).email, "test"
182     put :close, :id => changesets(:normal_user_first_change).id
183     assert_require_public_data
184
185
186     ## The try with the public user
187     basic_authorization users(:public_user).email, "test"
188
189     cs_id = changesets(:public_user_first_change).id
190     put :close, :id => cs_id
191     assert_response :success
192
193     # test that it really is closed now
194     cs = Changeset.find(cs_id)
195     assert(!cs.is_open?,
196            "changeset should be closed now (#{cs.closed_at} > #{Time.now.getutc}.")
197   end
198
199   ##
200   # test that a different user can't close another user's changeset
201   def test_close_invalid
202     basic_authorization users(:public_user).email, "test"
203
204     put :close, :id => changesets(:normal_user_first_change).id
205     assert_response :conflict
206     assert_equal "The user doesn't own that changeset", @response.body
207   end
208
209   ##
210   # test that you can't close using another method
211   def test_close_method_invalid
212     basic_authorization users(:public_user).email, "test"
213
214     cs_id = changesets(:public_user_first_change).id
215     get :close, :id => cs_id
216     assert_response :method_not_allowed
217
218     post :close, :id => cs_id
219     assert_response :method_not_allowed
220   end
221
222   ##
223   # check that you can't close a changeset that isn't found
224   def test_close_not_found
225     cs_ids = [0, -132, "123"]
226
227     # First try to do it with no auth
228     cs_ids.each do |id|
229       begin
230         put :close, :id => id
231         assert_response :unauthorized, "Shouldn't be able close the non-existant changeset #{id}, when not authorized"
232       rescue ActionController::UrlGenerationError => ex
233         assert_match /No route matches/, ex.to_s
234       end
235     end
236
237     # Now try with auth
238     basic_authorization users(:public_user).email, "test"
239     cs_ids.each do |id|
240       begin
241         put :close, :id => id
242         assert_response :not_found, "The changeset #{id} doesn't exist, so can't be closed"
243       rescue ActionController::UrlGenerationError => ex
244         assert_match /No route matches/, ex.to_s
245       end
246     end
247   end
248
249   ##
250   # upload something simple, but valid and check that it can
251   # be read back ok
252   # Also try without auth and another user.
253   def test_upload_simple_valid
254     ## Try with no auth
255     changeset_id = changesets(:public_user_first_change).id
256
257     # simple diff to change a node, way and relation by removing
258     # their tags
259     diff = <<EOF
260 <osmChange>
261  <modify>
262   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
263   <way id='1' changeset='#{changeset_id}' version='1'>
264    <nd ref='3'/>
265   </way>
266  </modify>
267  <modify>
268   <relation id='1' changeset='#{changeset_id}' version='1'>
269    <member type='way' role='some' ref='3'/>
270    <member type='node' role='some' ref='5'/>
271    <member type='relation' role='some' ref='3'/>
272   </relation>
273  </modify>
274 </osmChange>
275 EOF
276
277     # upload it
278     content diff
279     post :upload, :id => changeset_id
280     assert_response :unauthorized,
281       "shouldnn't be able to upload a simple valid diff to changeset: #{@response.body}"
282
283
284
285     ## Now try with a private user
286     basic_authorization users(:normal_user).email, "test"
287     changeset_id = changesets(:normal_user_first_change).id
288
289     # simple diff to change a node, way and relation by removing
290     # their tags
291     diff = <<EOF
292 <osmChange>
293  <modify>
294   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
295   <way id='1' changeset='#{changeset_id}' version='1'>
296    <nd ref='3'/>
297   </way>
298  </modify>
299  <modify>
300   <relation id='1' changeset='#{changeset_id}' version='1'>
301    <member type='way' role='some' ref='3'/>
302    <member type='node' role='some' ref='5'/>
303    <member type='relation' role='some' ref='3'/>
304   </relation>
305  </modify>
306 </osmChange>
307 EOF
308
309     # upload it
310     content diff
311     post :upload, :id => changeset_id
312     assert_response :forbidden,
313       "can't upload a simple valid diff to changeset: #{@response.body}"
314
315
316
317     ## Now try with the public user
318     basic_authorization users(:public_user).email, "test"
319     changeset_id = changesets(:public_user_first_change).id
320
321     # simple diff to change a node, way and relation by removing
322     # their tags
323     diff = <<EOF
324 <osmChange>
325  <modify>
326   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
327   <way id='1' changeset='#{changeset_id}' version='1'>
328    <nd ref='3'/>
329   </way>
330  </modify>
331  <modify>
332   <relation id='1' changeset='#{changeset_id}' version='1'>
333    <member type='way' role='some' ref='3'/>
334    <member type='node' role='some' ref='5'/>
335    <member type='relation' role='some' ref='3'/>
336   </relation>
337  </modify>
338 </osmChange>
339 EOF
340
341     # upload it
342     content diff
343     post :upload, :id => changeset_id
344     assert_response :success,
345       "can't upload a simple valid diff to changeset: #{@response.body}"
346
347     # check that the changes made it into the database
348     assert_equal 0, Node.find(1).tags.size, "node 1 should now have no tags"
349     assert_equal 0, Way.find(1).tags.size, "way 1 should now have no tags"
350     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
351   end
352
353   ##
354   # upload something which creates new objects using placeholders
355   def test_upload_create_valid
356     basic_authorization users(:public_user).email, "test"
357     cs_id = changesets(:public_user_first_change).id
358
359     # simple diff to create a node way and relation using placeholders
360     diff = <<EOF
361 <osmChange>
362  <create>
363   <node id='-1' lon='0' lat='0' changeset='#{cs_id}'>
364    <tag k='foo' v='bar'/>
365    <tag k='baz' v='bat'/>
366   </node>
367   <way id='-1' changeset='#{cs_id}'>
368    <nd ref='3'/>
369   </way>
370  </create>
371  <create>
372   <relation id='-1' changeset='#{cs_id}'>
373    <member type='way' role='some' ref='3'/>
374    <member type='node' role='some' ref='5'/>
375    <member type='relation' role='some' ref='3'/>
376   </relation>
377  </create>
378 </osmChange>
379 EOF
380
381     # upload it
382     content diff
383     post :upload, :id => cs_id
384     assert_response :success,
385       "can't upload a simple valid creation to changeset: #{@response.body}"
386
387     # check the returned payload
388     assert_select "diffResult[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
389     assert_select "diffResult>node", 1
390     assert_select "diffresult>way", 1
391     assert_select "diffResult>relation", 1
392
393     # inspect the response to find out what the new element IDs are
394     doc = XML::Parser.string(@response.body).parse
395     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
396     new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
397     new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
398
399     # check the old IDs are all present and negative one
400     assert_equal -1, doc.find("//diffResult/node").first["old_id"].to_i
401     assert_equal -1, doc.find("//diffResult/way").first["old_id"].to_i
402     assert_equal -1, doc.find("//diffResult/relation").first["old_id"].to_i
403
404     # check the versions are present and equal one
405     assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
406     assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
407     assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
408
409     # check that the changes made it into the database
410     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
411     assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
412     assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
413   end
414
415   ##
416   # test a complex delete where we delete elements which rely on eachother
417   # in the same transaction.
418   def test_upload_delete
419     basic_authorization users(:public_user).display_name, "test"
420
421     diff = XML::Document.new
422     diff.root = XML::Node.new "osmChange"
423     delete = XML::Node.new "delete"
424     diff.root << delete
425     delete << current_relations(:visible_relation).to_xml_node
426     delete << current_relations(:used_relation).to_xml_node
427     delete << current_ways(:used_way).to_xml_node
428     delete << current_nodes(:node_used_by_relationship).to_xml_node
429
430     # update the changeset to one that this user owns
431     changeset_id = changesets(:public_user_first_change).id
432     ["node", "way", "relation"].each do |type|
433       delete.find("//osmChange/delete/#{type}").each do |n|
434         n['changeset'] = changeset_id.to_s
435       end
436     end
437
438     # upload it
439     content diff
440     post :upload, :id => changeset_id
441     assert_response :success,
442       "can't upload a deletion diff to changeset: #{@response.body}"
443
444     # check the response is well-formed
445     assert_select "diffResult>node", 1
446     assert_select "diffResult>way", 1
447     assert_select "diffResult>relation", 2
448
449     # check that everything was deleted
450     assert_equal false, Node.find(current_nodes(:node_used_by_relationship).id).visible
451     assert_equal false, Way.find(current_ways(:used_way).id).visible
452     assert_equal false, Relation.find(current_relations(:visible_relation).id).visible
453     assert_equal false, Relation.find(current_relations(:used_relation).id).visible
454   end
455
456   ##
457   # test uploading a delete with no lat/lon, as they are optional in
458   # the osmChange spec.
459   def test_upload_nolatlon_delete
460     basic_authorization users(:public_user).display_name, "test"
461
462     node = current_nodes(:public_visible_node)
463     cs = changesets(:public_user_first_change)
464     diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{cs.id}'/></delete></osmChange>"
465
466     # upload it
467     content diff
468     post :upload, :id => cs.id
469     assert_response :success,
470       "can't upload a deletion diff to changeset: #{@response.body}"
471
472     # check the response is well-formed
473     assert_select "diffResult>node", 1
474
475     # check that everything was deleted
476     assert_equal false, Node.find(node.id).visible
477   end
478
479   def test_repeated_changeset_create
480     30.times do
481       basic_authorization users(:public_user).email, "test"
482
483       # create a temporary changeset
484       content "<osm><changeset>" +
485         "<tag k='created_by' v='osm test suite checking changesets'/>" +
486         "</changeset></osm>"
487       assert_difference('Changeset.count', 1) do
488         put :create
489       end
490       assert_response :success
491       changeset_id = @response.body.to_i
492     end
493   end
494
495   def test_upload_large_changeset
496     basic_authorization users(:public_user).email, "test"
497
498     # create a changeset
499     content "<osm><changeset/></osm>"
500     put :create
501     assert_response :success, "Should be able to create a changeset: #{@response.body}"
502     changeset_id = @response.body.to_i
503
504     # upload some widely-spaced nodes, spiralling positive and negative to cause
505     # largest bbox over-expansion possible.
506     diff = <<EOF
507 <osmChange>
508  <create>
509   <node id='-1' lon='-20' lat='-10' changeset='#{changeset_id}'/>
510   <node id='-10' lon='20'  lat='10' changeset='#{changeset_id}'/>
511   <node id='-2' lon='-40' lat='-20' changeset='#{changeset_id}'/>
512   <node id='-11' lon='40'  lat='20' changeset='#{changeset_id}'/>
513   <node id='-3' lon='-60' lat='-30' changeset='#{changeset_id}'/>
514   <node id='-12' lon='60'  lat='30' changeset='#{changeset_id}'/>
515   <node id='-4' lon='-80' lat='-40' changeset='#{changeset_id}'/>
516   <node id='-13' lon='80'  lat='40' changeset='#{changeset_id}'/>
517   <node id='-5' lon='-100' lat='-50' changeset='#{changeset_id}'/>
518   <node id='-14' lon='100'  lat='50' changeset='#{changeset_id}'/>
519   <node id='-6' lon='-120' lat='-60' changeset='#{changeset_id}'/>
520   <node id='-15' lon='120'  lat='60' changeset='#{changeset_id}'/>
521   <node id='-7' lon='-140' lat='-70' changeset='#{changeset_id}'/>
522   <node id='-16' lon='140'  lat='70' changeset='#{changeset_id}'/>
523   <node id='-8' lon='-160' lat='-80' changeset='#{changeset_id}'/>
524   <node id='-17' lon='160'  lat='80' changeset='#{changeset_id}'/>
525   <node id='-9' lon='-179.9' lat='-89.9' changeset='#{changeset_id}'/>
526   <node id='-18' lon='179.9'  lat='89.9' changeset='#{changeset_id}'/>
527  </create>
528 </osmChange>
529 EOF
530
531     # upload it, which used to cause an error like "PGError: ERROR:
532     # integer out of range" (bug #2152). but shouldn't any more.
533     content diff
534     post :upload, :id => changeset_id
535     assert_response :success,
536       "can't upload a spatially-large diff to changeset: #{@response.body}"
537
538     # check that the changeset bbox is within bounds
539     cs = Changeset.find(changeset_id)
540     assert cs.min_lon >= -180 * SCALE, "Minimum longitude (#{cs.min_lon / SCALE}) should be >= -180 to be valid."
541     assert cs.max_lon <=  180 * SCALE, "Maximum longitude (#{cs.max_lon / SCALE}) should be <= 180 to be valid."
542     assert cs.min_lat >=  -90 * SCALE, "Minimum latitude (#{cs.min_lat / SCALE}) should be >= -90 to be valid."
543     assert cs.max_lat >=   90 * SCALE, "Maximum latitude (#{cs.max_lat / SCALE}) should be <= 90 to be valid."
544   end
545
546   ##
547   # test that deleting stuff in a transaction doesn't bypass the checks
548   # to ensure that used elements are not deleted.
549   def test_upload_delete_invalid
550     basic_authorization users(:public_user).email, "test"
551
552     diff = XML::Document.new
553     diff.root = XML::Node.new "osmChange"
554     delete = XML::Node.new "delete"
555     diff.root << delete
556     delete << current_relations(:public_visible_relation).to_xml_node
557     delete << current_ways(:used_way).to_xml_node
558     delete << current_nodes(:node_used_by_relationship).to_xml_node
559
560     # upload it
561     content diff
562     post :upload, :id => 2
563     assert_response :precondition_failed,
564       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
565     assert_equal "Precondition failed: Way 3 is still used by relations 1.", @response.body
566
567     # check that nothing was, in fact, deleted
568     assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
569     assert_equal true, Way.find(current_ways(:used_way).id).visible
570     assert_equal true, Relation.find(current_relations(:visible_relation).id).visible
571   end
572
573   ##
574   # test that a conditional delete of an in use object works.
575   def test_upload_delete_if_unused
576     basic_authorization users(:public_user).email, "test"
577
578     diff = XML::Document.new
579     diff.root = XML::Node.new "osmChange"
580     delete = XML::Node.new "delete"
581     diff.root << delete
582     delete["if-unused"] = ""
583     delete << current_relations(:public_used_relation).to_xml_node
584     delete << current_ways(:used_way).to_xml_node
585     delete << current_nodes(:node_used_by_relationship).to_xml_node
586
587     # upload it
588     content diff
589     post :upload, :id => 2
590     assert_response :success,
591       "can't do a conditional delete of in use objects: #{@response.body}"
592
593     # check the returned payload
594     assert_select "diffResult[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
595     assert_select "diffResult>node", 1
596     assert_select "diffresult>way", 1
597     assert_select "diffResult>relation", 1
598
599     # parse the response
600     doc = XML::Parser.string(@response.body).parse
601
602     # check the old IDs are all present and what we expect
603     assert_equal current_nodes(:node_used_by_relationship).id, doc.find("//diffResult/node").first["old_id"].to_i
604     assert_equal current_ways(:used_way).id, doc.find("//diffResult/way").first["old_id"].to_i
605     assert_equal current_relations(:public_used_relation).id, doc.find("//diffResult/relation").first["old_id"].to_i
606
607     # check the new IDs are all present and unchanged
608     assert_equal current_nodes(:node_used_by_relationship).id, doc.find("//diffResult/node").first["new_id"].to_i
609     assert_equal current_ways(:used_way).id, doc.find("//diffResult/way").first["new_id"].to_i
610     assert_equal current_relations(:public_used_relation).id, doc.find("//diffResult/relation").first["new_id"].to_i
611
612     # check the new versions are all present and unchanged
613     assert_equal current_nodes(:node_used_by_relationship).version, doc.find("//diffResult/node").first["new_version"].to_i
614     assert_equal current_ways(:used_way).version, doc.find("//diffResult/way").first["new_version"].to_i
615     assert_equal current_relations(:public_used_relation).version, doc.find("//diffResult/relation").first["new_version"].to_i
616
617     # check that nothing was, in fact, deleted
618     assert_equal true, Node.find(current_nodes(:node_used_by_relationship).id).visible
619     assert_equal true, Way.find(current_ways(:used_way).id).visible
620     assert_equal true, Relation.find(current_relations(:public_used_relation).id).visible
621   end
622
623   ##
624   # upload an element with a really long tag value
625   def test_upload_invalid_too_long_tag
626     basic_authorization users(:public_user).email, "test"
627     cs_id = changesets(:public_user_first_change).id
628
629     # simple diff to create a node way and relation using placeholders
630     diff = <<EOF
631 <osmChange>
632  <create>
633   <node id='-1' lon='0' lat='0' changeset='#{cs_id}'>
634    <tag k='foo' v='#{"x"*256}'/>
635   </node>
636  </create>
637 </osmChange>
638 EOF
639
640     # upload it
641     content diff
642     post :upload, :id => cs_id
643     assert_response :bad_request,
644       "shoudln't be able to upload too long a tag to changeset: #{@response.body}"
645
646   end
647
648   ##
649   # upload something which creates new objects and inserts them into
650   # existing containers using placeholders.
651   def test_upload_complex
652     basic_authorization users(:public_user).email, "test"
653     cs_id = changesets(:public_user_first_change).id
654
655     # simple diff to create a node way and relation using placeholders
656     diff = <<EOF
657 <osmChange>
658  <create>
659   <node id='-1' lon='0' lat='0' changeset='#{cs_id}'>
660    <tag k='foo' v='bar'/>
661    <tag k='baz' v='bat'/>
662   </node>
663  </create>
664  <modify>
665   <way id='1' changeset='#{cs_id}' version='1'>
666    <nd ref='-1'/>
667    <nd ref='3'/>
668   </way>
669   <relation id='1' changeset='#{cs_id}' version='1'>
670    <member type='way' role='some' ref='3'/>
671    <member type='node' role='some' ref='-1'/>
672    <member type='relation' role='some' ref='3'/>
673   </relation>
674  </modify>
675 </osmChange>
676 EOF
677
678     # upload it
679     content diff
680     post :upload, :id => cs_id
681     assert_response :success,
682       "can't upload a complex diff to changeset: #{@response.body}"
683
684     # check the returned payload
685     assert_select "diffResult[version=#{API_VERSION}][generator=\"#{GENERATOR}\"]", 1
686     assert_select "diffResult>node", 1
687     assert_select "diffResult>way", 1
688     assert_select "diffResult>relation", 1
689
690     # inspect the response to find out what the new element IDs are
691     doc = XML::Parser.string(@response.body).parse
692     new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
693
694     # check that the changes made it into the database
695     assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
696     assert_equal [new_node_id, 3], Way.find(1).nds, "way nodes should match"
697     Relation.find(1).members.each do |type,id,role|
698       if type == 'node'
699         assert_equal new_node_id, id, "relation should contain new node"
700       end
701     end
702   end
703
704   ##
705   # create a diff which references several changesets, which should cause
706   # a rollback and none of the diff gets committed
707   def test_upload_invalid_changesets
708     basic_authorization users(:public_user).email, "test"
709     cs_id = changesets(:public_user_first_change).id
710
711     # simple diff to create a node way and relation using placeholders
712     diff = <<EOF
713 <osmChange>
714  <modify>
715   <node id='1' lon='0' lat='0' changeset='#{cs_id}' version='1'/>
716   <way id='1' changeset='#{cs_id}' version='1'>
717    <nd ref='3'/>
718   </way>
719  </modify>
720  <modify>
721   <relation id='1' changeset='#{cs_id}' version='1'>
722    <member type='way' role='some' ref='3'/>
723    <member type='node' role='some' ref='5'/>
724    <member type='relation' role='some' ref='3'/>
725   </relation>
726  </modify>
727  <create>
728   <node id='-1' lon='0' lat='0' changeset='4'>
729    <tag k='foo' v='bar'/>
730    <tag k='baz' v='bat'/>
731   </node>
732  </create>
733 </osmChange>
734 EOF
735     # cache the objects before uploading them
736     node = current_nodes(:visible_node)
737     way = current_ways(:visible_way)
738     rel = current_relations(:visible_relation)
739
740     # upload it
741     content diff
742     post :upload, :id => cs_id
743     assert_response :conflict,
744       "uploading a diff with multiple changsets should have failed"
745
746     # check that objects are unmodified
747     assert_nodes_are_equal(node, Node.find(1))
748     assert_ways_are_equal(way, Way.find(1))
749   end
750
751   ##
752   # upload multiple versions of the same element in the same diff.
753   def test_upload_multiple_valid
754     basic_authorization users(:public_user).email, "test"
755     cs_id = changesets(:public_user_first_change).id
756
757     # change the location of a node multiple times, each time referencing
758     # the last version. doesn't this depend on version numbers being
759     # sequential?
760     diff = <<EOF
761 <osmChange>
762  <modify>
763   <node id='1' lon='0' lat='0' changeset='#{cs_id}' version='1'/>
764   <node id='1' lon='1' lat='0' changeset='#{cs_id}' version='2'/>
765   <node id='1' lon='1' lat='1' changeset='#{cs_id}' version='3'/>
766   <node id='1' lon='1' lat='2' changeset='#{cs_id}' version='4'/>
767   <node id='1' lon='2' lat='2' changeset='#{cs_id}' version='5'/>
768   <node id='1' lon='3' lat='2' changeset='#{cs_id}' version='6'/>
769   <node id='1' lon='3' lat='3' changeset='#{cs_id}' version='7'/>
770   <node id='1' lon='9' lat='9' changeset='#{cs_id}' version='8'/>
771  </modify>
772 </osmChange>
773 EOF
774
775     # upload it
776     content diff
777     post :upload, :id => cs_id
778     assert_response :success,
779       "can't upload multiple versions of an element in a diff: #{@response.body}"
780
781     # check the response is well-formed. its counter-intuitive, but the
782     # API will return multiple elements with the same ID and different
783     # version numbers for each change we made.
784     assert_select "diffResult>node", 8
785   end
786
787   ##
788   # upload multiple versions of the same element in the same diff, but
789   # keep the version numbers the same.
790   def test_upload_multiple_duplicate
791     basic_authorization users(:public_user).email, "test"
792     cs_id = changesets(:public_user_first_change).id
793
794     diff = <<EOF
795 <osmChange>
796  <modify>
797   <node id='1' lon='0' lat='0' changeset='#{cs_id}' version='1'/>
798   <node id='1' lon='1' lat='1' changeset='#{cs_id}' version='1'/>
799  </modify>
800 </osmChange>
801 EOF
802
803     # upload it
804     content diff
805     post :upload, :id => cs_id
806     assert_response :conflict,
807       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
808   end
809
810   ##
811   # try to upload some elements without specifying the version
812   def test_upload_missing_version
813     basic_authorization users(:public_user).email, "test"
814     cs_id = changesets(:public_user_first_change).id
815
816     diff = <<EOF
817 <osmChange>
818  <modify>
819  <node id='1' lon='1' lat='1' changeset='cs_id'/>
820  </modify>
821 </osmChange>
822 EOF
823
824     # upload it
825     content diff
826     post :upload, :id => cs_id
827     assert_response :bad_request,
828       "shouldn't be able to upload an element without version: #{@response.body}"
829   end
830
831   ##
832   # try to upload with commands other than create, modify, or delete
833   def test_action_upload_invalid
834     basic_authorization users(:public_user).email, "test"
835     cs_id = changesets(:public_user_first_change).id
836
837     diff = <<EOF
838 <osmChange>
839   <ping>
840    <node id='1' lon='1' lat='1' changeset='#{cs_id}' />
841   </ping>
842 </osmChange>
843 EOF
844   content diff
845   post :upload, :id => cs_id
846   assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
847   assert_equal @response.body, "Unknown action ping, choices are create, modify, delete"
848   end
849
850   ##
851   # upload a valid changeset which has a mixture of whitespace
852   # to check a bug reported by ivansanchez (#1565).
853   def test_upload_whitespace_valid
854     basic_authorization users(:public_user).email, "test"
855     changeset_id = changesets(:public_user_first_change).id
856
857     diff = <<EOF
858 <osmChange>
859  <modify><node id='1' lon='0' lat='0' changeset='#{changeset_id}'
860   version='1'></node>
861   <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='2'><tag k='k' v='v'/></node></modify>
862  <modify>
863  <relation id='1' changeset='#{changeset_id}' version='1'><member
864    type='way' role='some' ref='3'/><member
865     type='node' role='some' ref='5'/>
866    <member type='relation' role='some' ref='3'/>
867   </relation>
868  </modify></osmChange>
869 EOF
870
871     # upload it
872     content diff
873     post :upload, :id => changeset_id
874     assert_response :success,
875       "can't upload a valid diff with whitespace variations to changeset: #{@response.body}"
876
877     # check the response is well-formed
878     assert_select "diffResult>node", 2
879     assert_select "diffResult>relation", 1
880
881     # check that the changes made it into the database
882     assert_equal 1, Node.find(1).tags.size, "node 1 should now have one tag"
883     assert_equal 0, Relation.find(1).tags.size, "relation 1 should now have no tags"
884   end
885
886   ##
887   # upload a valid changeset which has a mixture of whitespace
888   # to check a bug reported by ivansanchez.
889   def test_upload_reuse_placeholder_valid
890     basic_authorization users(:public_user).email, "test"
891     changeset_id = changesets(:public_user_first_change).id
892
893     diff = <<EOF
894 <osmChange>
895  <create>
896   <node id='-1' lon='0' lat='0' changeset='#{changeset_id}'>
897    <tag k="foo" v="bar"/>
898   </node>
899  </create>
900  <modify>
901   <node id='-1' lon='1' lat='1' changeset='#{changeset_id}' version='1'/>
902  </modify>
903  <delete>
904   <node id='-1' lon='2' lat='2' changeset='#{changeset_id}' version='2'/>
905  </delete>
906 </osmChange>
907 EOF
908
909     # upload it
910     content diff
911     post :upload, :id => changeset_id
912     assert_response :success,
913       "can't upload a valid diff with re-used placeholders to changeset: #{@response.body}"
914
915     # check the response is well-formed
916     assert_select "diffResult>node", 3
917     assert_select "diffResult>node[old_id=-1]", 3
918   end
919
920   ##
921   # test what happens if a diff upload re-uses placeholder IDs in an
922   # illegal way.
923   def test_upload_placeholder_invalid
924     basic_authorization users(:public_user).email, "test"
925     changeset_id = changesets(:public_user_first_change).id
926
927     diff = <<EOF
928 <osmChange>
929  <create>
930   <node id='-1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
931   <node id='-1' lon='1' lat='1' changeset='#{changeset_id}' version='1'/>
932   <node id='-1' lon='2' lat='2' changeset='#{changeset_id}' version='2'/>
933  </create>
934 </osmChange>
935 EOF
936
937     # upload it
938     content diff
939     post :upload, :id => changeset_id
940     assert_response :bad_request,
941       "shouldn't be able to re-use placeholder IDs"
942   end
943
944   ##
945   # test that uploading a way referencing invalid placeholders gives a
946   # proper error, not a 500.
947   def test_upload_placeholder_invalid_way
948     basic_authorization users(:public_user).email, "test"
949     changeset_id = changesets(:public_user_first_change).id
950
951     diff = <<EOF
952 <osmChange>
953  <create>
954   <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
955   <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
956   <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
957   <way id="-1" changeset="#{changeset_id}" version="1">
958    <nd ref="-1"/>
959    <nd ref="-2"/>
960    <nd ref="-3"/>
961    <nd ref="-4"/>
962   </way>
963  </create>
964 </osmChange>
965 EOF
966
967     # upload it
968     content diff
969     post :upload, :id => changeset_id
970     assert_response :bad_request,
971       "shouldn't be able to use invalid placeholder IDs"
972     assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
973
974     # the same again, but this time use an existing way
975     diff = <<EOF
976 <osmChange>
977  <create>
978   <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
979   <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
980   <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
981   <way id="1" changeset="#{changeset_id}" version="1">
982    <nd ref="-1"/>
983    <nd ref="-2"/>
984    <nd ref="-3"/>
985    <nd ref="-4"/>
986   </way>
987  </create>
988 </osmChange>
989 EOF
990
991     # upload it
992     content diff
993     post :upload, :id => changeset_id
994     assert_response :bad_request,
995       "shouldn't be able to use invalid placeholder IDs"
996     assert_equal "Placeholder node not found for reference -4 in way 1", @response.body
997   end
998
999   ##
1000   # test that uploading a relation referencing invalid placeholders gives a
1001   # proper error, not a 500.
1002   def test_upload_placeholder_invalid_relation
1003     basic_authorization users(:public_user).email, "test"
1004     changeset_id = changesets(:public_user_first_change).id
1005
1006     diff = <<EOF
1007 <osmChange>
1008  <create>
1009   <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
1010   <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
1011   <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
1012   <relation id="-1" changeset="#{changeset_id}" version="1">
1013    <member type="node" role="foo" ref="-1"/>
1014    <member type="node" role="foo" ref="-2"/>
1015    <member type="node" role="foo" ref="-3"/>
1016    <member type="node" role="foo" ref="-4"/>
1017   </relation>
1018  </create>
1019 </osmChange>
1020 EOF
1021
1022     # upload it
1023     content diff
1024     post :upload, :id => changeset_id
1025     assert_response :bad_request,
1026       "shouldn't be able to use invalid placeholder IDs"
1027     assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
1028
1029     # the same again, but this time use an existing way
1030     diff = <<EOF
1031 <osmChange>
1032  <create>
1033   <node id="-1" lon="0" lat="0" changeset="#{changeset_id}" version="1"/>
1034   <node id="-2" lon="1" lat="1" changeset="#{changeset_id}" version="1"/>
1035   <node id="-3" lon="2" lat="2" changeset="#{changeset_id}" version="1"/>
1036   <relation id="1" changeset="#{changeset_id}" version="1">
1037    <member type="node" role="foo" ref="-1"/>
1038    <member type="node" role="foo" ref="-2"/>
1039    <member type="node" role="foo" ref="-3"/>
1040    <member type="way" role="bar" ref="-1"/>
1041   </relation>
1042  </create>
1043 </osmChange>
1044 EOF
1045
1046     # upload it
1047     content diff
1048     post :upload, :id => changeset_id
1049     assert_response :bad_request,
1050       "shouldn't be able to use invalid placeholder IDs"
1051     assert_equal "Placeholder Way not found for reference -1 in relation 1.", @response.body
1052   end
1053
1054   ##
1055   # test what happens if a diff is uploaded containing only a node
1056   # move.
1057   def test_upload_node_move
1058     basic_authorization users(:public_user).email, "test"
1059
1060     content "<osm><changeset>" +
1061       "<tag k='created_by' v='osm test suite checking changesets'/>" +
1062       "</changeset></osm>"
1063     put :create
1064     assert_response :success
1065     changeset_id = @response.body.to_i
1066
1067     old_node = current_nodes(:visible_node)
1068
1069     diff = XML::Document.new
1070     diff.root = XML::Node.new "osmChange"
1071     modify = XML::Node.new "modify"
1072     xml_old_node = old_node.to_xml_node
1073     xml_old_node["lat"] = (2.0).to_s
1074     xml_old_node["lon"] = (2.0).to_s
1075     xml_old_node["changeset"] = changeset_id.to_s
1076     modify << xml_old_node
1077     diff.root << modify
1078
1079     # upload it
1080     content diff
1081     post :upload, :id => changeset_id
1082     assert_response :success,
1083       "diff should have uploaded OK"
1084
1085     # check the bbox
1086     changeset = Changeset.find(changeset_id)
1087     assert_equal 1*SCALE, changeset.min_lon, "min_lon should be 1 degree"
1088     assert_equal 2*SCALE, changeset.max_lon, "max_lon should be 2 degrees"
1089     assert_equal 1*SCALE, changeset.min_lat, "min_lat should be 1 degree"
1090     assert_equal 2*SCALE, changeset.max_lat, "max_lat should be 2 degrees"
1091   end
1092
1093   ##
1094   # test what happens if a diff is uploaded adding a node to a way.
1095   def test_upload_way_extend
1096     basic_authorization users(:public_user).email, "test"
1097
1098     content "<osm><changeset>" +
1099       "<tag k='created_by' v='osm test suite checking changesets'/>" +
1100       "</changeset></osm>"
1101     put :create
1102     assert_response :success
1103     changeset_id = @response.body.to_i
1104
1105     old_way = current_ways(:visible_way)
1106
1107     diff = XML::Document.new
1108     diff.root = XML::Node.new "osmChange"
1109     modify = XML::Node.new "modify"
1110     xml_old_way = old_way.to_xml_node
1111     nd_ref = XML::Node.new "nd"
1112     nd_ref["ref"] = current_nodes(:visible_node).id.to_s
1113     xml_old_way << nd_ref
1114     xml_old_way["changeset"] = changeset_id.to_s
1115     modify << xml_old_way
1116     diff.root << modify
1117
1118     # upload it
1119     content diff
1120     post :upload, :id => changeset_id
1121     assert_response :success,
1122       "diff should have uploaded OK"
1123
1124     # check the bbox
1125     changeset = Changeset.find(changeset_id)
1126     assert_equal 1*SCALE, changeset.min_lon, "min_lon should be 1 degree"
1127     assert_equal 3*SCALE, changeset.max_lon, "max_lon should be 3 degrees"
1128     assert_equal 1*SCALE, changeset.min_lat, "min_lat should be 1 degree"
1129     assert_equal 3*SCALE, changeset.max_lat, "max_lat should be 3 degrees"
1130   end
1131
1132   ##
1133   # test for more issues in #1568
1134   def test_upload_empty_invalid
1135     basic_authorization users(:public_user).email, "test"
1136
1137     [ "<osmChange/>",
1138       "<osmChange></osmChange>",
1139       "<osmChange><modify/></osmChange>",
1140       "<osmChange><modify></modify></osmChange>"
1141     ].each do |diff|
1142       # upload it
1143       content diff
1144       post :upload, :id => changesets(:public_user_first_change).id
1145       assert_response(:success, "should be able to upload " +
1146                       "empty changeset: " + diff)
1147     end
1148   end
1149
1150   ##
1151   # test that the X-Error-Format header works to request XML errors
1152   def test_upload_xml_errors
1153     basic_authorization users(:public_user).email, "test"
1154
1155     # try and delete a node that is in use
1156     diff = XML::Document.new
1157     diff.root = XML::Node.new "osmChange"
1158     delete = XML::Node.new "delete"
1159     diff.root << delete
1160     delete << current_nodes(:node_used_by_relationship).to_xml_node
1161
1162     # upload it
1163     content diff
1164     error_format "xml"
1165     post :upload, :id => 2
1166     assert_response :success,
1167       "failed to return error in XML format"
1168
1169     # check the returned payload
1170     assert_select "osmError[version=#{API_VERSION}][generator=\"OpenStreetMap server\"]", 1
1171     assert_select "osmError>status", 1
1172     assert_select "osmError>message", 1
1173
1174   end
1175
1176   ##
1177   # when we make some simple changes we get the same changes back from the
1178   # diff download.
1179   def test_diff_download_simple
1180     ## First try with the normal user, which should get a forbidden
1181     basic_authorization(users(:normal_user).email, "test")
1182
1183     # create a temporary changeset
1184     content "<osm><changeset>" +
1185       "<tag k='created_by' v='osm test suite checking changesets'/>" +
1186       "</changeset></osm>"
1187     put :create
1188     assert_response :forbidden
1189
1190
1191
1192     ## Now try with the public user
1193     basic_authorization(users(:public_user).email, "test")
1194
1195     # create a temporary changeset
1196     content "<osm><changeset>" +
1197       "<tag k='created_by' v='osm test suite checking changesets'/>" +
1198       "</changeset></osm>"
1199     put :create
1200     assert_response :success
1201     changeset_id = @response.body.to_i
1202
1203     # add a diff to it
1204     diff = <<EOF
1205 <osmChange>
1206  <modify>
1207   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1208   <node id='1' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
1209   <node id='1' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
1210   <node id='1' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
1211   <node id='1' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
1212   <node id='1' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
1213   <node id='1' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
1214   <node id='1' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
1215  </modify>
1216 </osmChange>
1217 EOF
1218
1219     # upload it
1220     content diff
1221     post :upload, :id => changeset_id
1222     assert_response :success,
1223       "can't upload multiple versions of an element in a diff: #{@response.body}"
1224
1225     get :download, :id => changeset_id
1226     assert_response :success
1227
1228     assert_select "osmChange", 1
1229     assert_select "osmChange>modify", 8
1230     assert_select "osmChange>modify>node", 8
1231   end
1232
1233   ##
1234   # culled this from josm to ensure that nothing in the way that josm
1235   # is formatting the request is causing it to fail.
1236   #
1237   # NOTE: the error turned out to be something else completely!
1238   def test_josm_upload
1239     basic_authorization(users(:public_user).email, "test")
1240
1241     # create a temporary changeset
1242     content "<osm><changeset>" +
1243       "<tag k='created_by' v='osm test suite checking changesets'/>" +
1244       "</changeset></osm>"
1245     put :create
1246     assert_response :success
1247     changeset_id = @response.body.to_i
1248
1249     diff = <<OSMFILE
1250 <osmChange version="0.6" generator="JOSM">
1251 <create version="0.6" generator="JOSM">
1252   <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
1253   <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
1254   <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
1255   <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
1256   <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
1257   <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
1258   <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
1259   <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
1260   <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
1261   <way id='-10' action='modiy' visible='true' changeset='#{changeset_id}'>
1262     <nd ref='-1' />
1263     <nd ref='-2' />
1264     <nd ref='-3' />
1265     <nd ref='-4' />
1266     <nd ref='-5' />
1267     <nd ref='-6' />
1268     <nd ref='-7' />
1269     <nd ref='-8' />
1270     <nd ref='-9' />
1271     <tag k='highway' v='residential' />
1272     <tag k='name' v='Foobar Street' />
1273   </way>
1274 </create>
1275 </osmChange>
1276 OSMFILE
1277
1278     # upload it
1279     content diff
1280     post :upload, :id => changeset_id
1281     assert_response :success,
1282       "can't upload a diff from JOSM: #{@response.body}"
1283
1284     get :download, :id => changeset_id
1285     assert_response :success
1286
1287     assert_select "osmChange", 1
1288     assert_select "osmChange>create>node", 9
1289     assert_select "osmChange>create>way", 1
1290     assert_select "osmChange>create>way>nd", 9
1291     assert_select "osmChange>create>way>tag", 2
1292   end
1293
1294   ##
1295   # when we make some complex changes we get the same changes back from the
1296   # diff download.
1297   def test_diff_download_complex
1298     basic_authorization(users(:public_user).email, "test")
1299
1300     # create a temporary changeset
1301     content "<osm><changeset>" +
1302       "<tag k='created_by' v='osm test suite checking changesets'/>" +
1303       "</changeset></osm>"
1304     put :create
1305     assert_response :success
1306     changeset_id = @response.body.to_i
1307
1308     # add a diff to it
1309     diff = <<EOF
1310 <osmChange>
1311  <delete>
1312   <node id='1' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1313  </delete>
1314  <create>
1315   <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
1316   <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
1317   <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
1318  </create>
1319  <modify>
1320   <node id='3' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
1321   <way id='1' changeset='#{changeset_id}' version='1'>
1322    <nd ref='3'/>
1323    <nd ref='-1'/>
1324    <nd ref='-2'/>
1325    <nd ref='-3'/>
1326   </way>
1327  </modify>
1328 </osmChange>
1329 EOF
1330
1331     # upload it
1332     content diff
1333     post :upload, :id => changeset_id
1334     assert_response :success,
1335       "can't upload multiple versions of an element in a diff: #{@response.body}"
1336
1337     get :download, :id => changeset_id
1338     assert_response :success
1339
1340     assert_select "osmChange", 1
1341     assert_select "osmChange>create", 3
1342     assert_select "osmChange>delete", 1
1343     assert_select "osmChange>modify", 2
1344     assert_select "osmChange>create>node", 3
1345     assert_select "osmChange>delete>node", 1
1346     assert_select "osmChange>modify>node", 1
1347     assert_select "osmChange>modify>way", 1
1348   end
1349
1350   def test_changeset_download
1351     get :download, :id => changesets(:normal_user_first_change).id
1352     assert_response :success
1353     assert_template nil
1354     #print @response.body
1355     # FIXME needs more assert_select tests
1356     assert_select "osmChange[version='#{API_VERSION}'][generator='#{GENERATOR}']" do
1357       assert_select "create", :count => 5
1358       assert_select "create>node[id=#{nodes(:used_node_2).node_id}][visible=#{nodes(:used_node_2).visible?}][version=#{nodes(:used_node_2).version}]" do
1359         assert_select "tag[k=#{node_tags(:t3).k}][v=#{node_tags(:t3).v}]"
1360       end
1361       assert_select "create>node[id=#{nodes(:visible_node).node_id}]"
1362     end
1363   end
1364
1365   ##
1366   # check that the bounding box of a changeset gets updated correctly
1367   ## FIXME: This should really be moded to a integration test due to the with_controller
1368   def test_changeset_bbox
1369     basic_authorization users(:public_user).email, "test"
1370
1371     # create a new changeset
1372     content "<osm><changeset/></osm>"
1373     put :create
1374     assert_response :success, "Creating of changeset failed."
1375     changeset_id = @response.body.to_i
1376
1377     # add a single node to it
1378     with_controller(NodeController.new) do
1379       content "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
1380       put :create
1381       assert_response :success, "Couldn't create node."
1382     end
1383
1384     # get the bounding box back from the changeset
1385     get :read, :id => changeset_id
1386     assert_response :success, "Couldn't read back changeset."
1387     assert_select "osm>changeset[min_lon=1.0]", 1
1388     assert_select "osm>changeset[max_lon=1.0]", 1
1389     assert_select "osm>changeset[min_lat=2.0]", 1
1390     assert_select "osm>changeset[max_lat=2.0]", 1
1391
1392     # add another node to it
1393     with_controller(NodeController.new) do
1394       content "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
1395       put :create
1396       assert_response :success, "Couldn't create second node."
1397     end
1398
1399     # get the bounding box back from the changeset
1400     get :read, :id => changeset_id
1401     assert_response :success, "Couldn't read back changeset for the second time."
1402     assert_select "osm>changeset[min_lon=1.0]", 1
1403     assert_select "osm>changeset[max_lon=2.0]", 1
1404     assert_select "osm>changeset[min_lat=1.0]", 1
1405     assert_select "osm>changeset[max_lat=2.0]", 1
1406
1407     # add (delete) a way to it, which contains a point at (3,3)
1408     with_controller(WayController.new) do
1409       content update_changeset(current_ways(:visible_way).to_xml,
1410                                changeset_id)
1411       put :delete, :id => current_ways(:visible_way).id
1412       assert_response :success, "Couldn't delete a way."
1413     end
1414
1415     # get the bounding box back from the changeset
1416     get :read, :id => changeset_id
1417     assert_response :success, "Couldn't read back changeset for the third time."
1418     # note that the 3.1 here is because of the bbox overexpansion
1419     assert_select "osm>changeset[min_lon=1.0]", 1
1420     assert_select "osm>changeset[max_lon=3.1]", 1
1421     assert_select "osm>changeset[min_lat=1.0]", 1
1422     assert_select "osm>changeset[max_lat=3.1]", 1
1423   end
1424
1425   ##
1426   # test that the changeset :include method works as it should
1427   def test_changeset_include
1428     basic_authorization users(:public_user).display_name, "test"
1429
1430     # create a new changeset
1431     content "<osm><changeset/></osm>"
1432     put :create
1433     assert_response :success, "Creating of changeset failed."
1434     changeset_id = @response.body.to_i
1435
1436     # NOTE: the include method doesn't over-expand, like inserting
1437     # a real method does. this is because we expect the client to
1438     # know what it is doing!
1439     check_after_include(changeset_id,  1,  1, [ 1,  1,  1,  1])
1440     check_after_include(changeset_id,  3,  3, [ 1,  1,  3,  3])
1441     check_after_include(changeset_id,  4,  2, [ 1,  1,  4,  3])
1442     check_after_include(changeset_id,  2,  2, [ 1,  1,  4,  3])
1443     check_after_include(changeset_id, -1, -1, [-1, -1,  4,  3])
1444     check_after_include(changeset_id, -2,  5, [-2, -1,  4,  5])
1445   end
1446
1447   ##
1448   # test that a not found, wrong method with the expand bbox works as expected
1449   def test_changeset_expand_bbox_error
1450     basic_authorization users(:public_user).display_name, "test"
1451
1452     # create a new changeset
1453     content "<osm><changeset/></osm>"
1454     put :create
1455     assert_response :success, "Creating of changeset failed."
1456     changeset_id = @response.body.to_i
1457
1458     lon=58.2
1459     lat=-0.45
1460
1461     # Try and put
1462     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
1463     put :expand_bbox, :id => changeset_id
1464     assert_response :method_not_allowed, "shouldn't be able to put a bbox expand"
1465
1466     # Try to get the update
1467     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
1468     get :expand_bbox, :id => changeset_id
1469     assert_response :method_not_allowed, "shouldn't be able to get a bbox expand"
1470
1471     # Try to use a hopefully missing changeset
1472     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
1473     post :expand_bbox, :id => changeset_id+13245
1474     assert_response :not_found, "shouldn't be able to do a bbox expand on a nonexistant changeset"
1475
1476   end
1477
1478   ##
1479   # test the query functionality of changesets
1480   def test_query
1481     get :query, :bbox => "-10,-10, 10, 10"
1482     assert_response :success, "can't get changesets in bbox"
1483     assert_changesets [1,4,6]
1484
1485     get :query, :bbox => "4.5,4.5,4.6,4.6"
1486     assert_response :success, "can't get changesets in bbox"
1487     assert_changesets [1]
1488
1489     # not found when looking for changesets of non-existing users
1490     get :query, :user => User.maximum(:id) + 1
1491     assert_response :not_found
1492     get :query, :display_name => " "
1493     assert_response :not_found
1494
1495     # can't get changesets of user 1 without authenticating
1496     get :query, :user => users(:normal_user).id
1497     assert_response :not_found, "shouldn't be able to get changesets by non-public user (ID)"
1498     get :query, :display_name => users(:normal_user).display_name
1499     assert_response :not_found, "shouldn't be able to get changesets by non-public user (name)"
1500
1501     # but this should work
1502     basic_authorization "test@openstreetmap.org", "test"
1503     get :query, :user => users(:normal_user).id
1504     assert_response :success, "can't get changesets by user ID"
1505     assert_changesets [1,3,6]
1506
1507     get :query, :display_name => users(:normal_user).display_name
1508     assert_response :success, "can't get changesets by user name"
1509     assert_changesets [1,3,6]
1510
1511     # check that the correct error is given when we provide both UID and name
1512     get :query, :user => users(:normal_user).id, :display_name => users(:normal_user).display_name
1513     assert_response :bad_request, "should be a bad request to have both ID and name specified"
1514
1515     get :query, :user => users(:normal_user).id, :open => true
1516     assert_response :success, "can't get changesets by user and open"
1517     assert_changesets [1]
1518
1519     get :query, :time => '2007-12-31'
1520     assert_response :success, "can't get changesets by time-since"
1521     assert_changesets [1,2,4,5,6]
1522
1523     get :query, :time => '2008-01-01T12:34Z'
1524     assert_response :success, "can't get changesets by time-since with hour"
1525     assert_changesets [1,2,4,5,6]
1526
1527     get :query, :time => '2007-12-31T23:59Z,2008-01-01T00:01Z'
1528     assert_response :success, "can't get changesets by time-range"
1529     assert_changesets [1,5,6]
1530
1531     get :query, :open => 'true'
1532     assert_response :success, "can't get changesets by open-ness"
1533     assert_changesets [1,2,4]
1534
1535     get :query, :closed => 'true'
1536     assert_response :success, "can't get changesets by closed-ness"
1537     assert_changesets [3,5,6,7]
1538
1539     get :query, :closed => 'true', :user => users(:normal_user).id
1540     assert_response :success, "can't get changesets by closed-ness and user"
1541     assert_changesets [3,6]
1542
1543     get :query, :closed => 'true', :user => users(:public_user).id
1544     assert_response :success, "can't get changesets by closed-ness and user"
1545     assert_changesets [7]
1546
1547     get :query, :changesets => '1,2,3'
1548     assert_response :success, "can't get changesets by id (as comma-separated string)"
1549     assert_changesets [1,2,3]
1550
1551     get :query, :changesets => ''
1552     assert_response :bad_request, "should be a bad request since changesets is empty"
1553   end
1554
1555   ##
1556   # check that errors are returned if garbage is inserted
1557   # into query strings
1558   def test_query_invalid
1559     [ "abracadabra!",
1560       "1,2,3,F",
1561       ";drop table users;"
1562       ].each do |bbox|
1563       get :query, :bbox => bbox
1564       assert_response :bad_request, "'#{bbox}' isn't a bbox"
1565     end
1566
1567     [ "now()",
1568       "00-00-00",
1569       ";drop table users;",
1570       ",",
1571       "-,-"
1572       ].each do |time|
1573       get :query, :time => time
1574       assert_response :bad_request, "'#{time}' isn't a valid time range"
1575     end
1576
1577     [ "me",
1578       "foobar",
1579       "-1",
1580       "0"
1581       ].each do |uid|
1582       get :query, :user => uid
1583       assert_response :bad_request, "'#{uid}' isn't a valid user ID"
1584     end
1585   end
1586
1587   ##
1588   # check updating tags on a changeset
1589   def test_changeset_update
1590     ## First try with the non-public user
1591     changeset = changesets(:normal_user_first_change)
1592     new_changeset = changeset.to_xml
1593     new_tag = XML::Node.new "tag"
1594     new_tag['k'] = "tagtesting"
1595     new_tag['v'] = "valuetesting"
1596     new_changeset.find("//osm/changeset").first << new_tag
1597     content new_changeset
1598
1599     # try without any authorization
1600     put :update, :id => changeset.id
1601     assert_response :unauthorized
1602
1603     # try with the wrong authorization
1604     basic_authorization users(:public_user).email, "test"
1605     put :update, :id => changeset.id
1606     assert_response :conflict
1607
1608     # now this should get an unauthorized
1609     basic_authorization users(:normal_user).email, "test"
1610     put :update, :id => changeset.id
1611     assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
1612
1613
1614     ## Now try with the public user
1615     changeset = changesets(:public_user_first_change)
1616     new_changeset = changeset.to_xml
1617     new_tag = XML::Node.new "tag"
1618     new_tag['k'] = "tagtesting"
1619     new_tag['v'] = "valuetesting"
1620     new_changeset.find("//osm/changeset").first << new_tag
1621     content new_changeset
1622
1623     # try without any authorization
1624     @request.env["HTTP_AUTHORIZATION"] = nil
1625     put :update, :id => changeset.id
1626     assert_response :unauthorized
1627
1628     # try with the wrong authorization
1629     basic_authorization users(:second_public_user).email, "test"
1630     put :update, :id => changeset.id
1631     assert_response :conflict
1632
1633     # now this should work...
1634     basic_authorization users(:public_user).email, "test"
1635     put :update, :id => changeset.id
1636     assert_response :success
1637
1638     assert_select "osm>changeset[id=#{changeset.id}]", 1
1639     assert_select "osm>changeset>tag", 2
1640     assert_select "osm>changeset>tag[k=tagtesting][v=valuetesting]", 1
1641   end
1642
1643   ##
1644   # check that a user different from the one who opened the changeset
1645   # can't modify it.
1646   def test_changeset_update_invalid
1647     basic_authorization users(:public_user).email, "test"
1648
1649     changeset = changesets(:normal_user_first_change)
1650     new_changeset = changeset.to_xml
1651     new_tag = XML::Node.new "tag"
1652     new_tag['k'] = "testing"
1653     new_tag['v'] = "testing"
1654     new_changeset.find("//osm/changeset").first << new_tag
1655
1656     content new_changeset
1657     put :update, :id => changeset.id
1658     assert_response :conflict
1659   end
1660
1661   ##
1662   # check that a changeset can contain a certain max number of changes.
1663   ## FIXME should be changed to an integration test due to the with_controller
1664   def test_changeset_limits
1665     basic_authorization users(:public_user).email, "test"
1666
1667     # open a new changeset
1668     content "<osm><changeset/></osm>"
1669     put :create
1670     assert_response :success, "can't create a new changeset"
1671     cs_id = @response.body.to_i
1672
1673     # start the counter just short of where the changeset should finish.
1674     offset = 10
1675     # alter the database to set the counter on the changeset directly,
1676     # otherwise it takes about 6 minutes to fill all of them.
1677     changeset = Changeset.find(cs_id)
1678     changeset.num_changes = Changeset::MAX_ELEMENTS - offset
1679     changeset.save!
1680
1681     with_controller(NodeController.new) do
1682       # create a new node
1683       content "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
1684       put :create
1685       assert_response :success, "can't create a new node"
1686       node_id = @response.body.to_i
1687
1688       get :read, :id => node_id
1689       assert_response :success, "can't read back new node"
1690       node_doc = XML::Parser.string(@response.body).parse
1691       node_xml = node_doc.find("//osm/node").first
1692
1693       # loop until we fill the changeset with nodes
1694       offset.times do |i|
1695         node_xml['lat'] = rand.to_s
1696         node_xml['lon'] = rand.to_s
1697         node_xml['version'] = (i+1).to_s
1698
1699         content node_doc
1700         put :update, :id => node_id
1701         assert_response :success, "attempt #{i} should have succeeded"
1702       end
1703
1704       # trying again should fail
1705       node_xml['lat'] = rand.to_s
1706       node_xml['lon'] = rand.to_s
1707       node_xml['version'] = offset.to_s
1708
1709       content node_doc
1710       put :update, :id => node_id
1711       assert_response :conflict, "final attempt should have failed"
1712     end
1713
1714     changeset = Changeset.find(cs_id)
1715     assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
1716
1717     # check that the changeset is now closed as well
1718     assert(!changeset.is_open?,
1719            "changeset should have been auto-closed by exceeding " +
1720            "element limit.")
1721   end
1722
1723   ##
1724   # This should display the last 20 changesets closed.
1725   def test_list
1726     get :list, {:format => "html"}
1727     assert_response :success
1728     assert_template "history"
1729     assert_template :layout => "map"
1730     assert_select "h2", :text => "Changesets", :count => 1
1731
1732     get :list, {:format => "html", :list => '1', :bbox => '-180,-90,90,180'}
1733     assert_response :success
1734     assert_template "list"
1735
1736     changesets = Changeset.
1737         where("num_changes > 0 and min_lon is not null").
1738         order(:created_at => :desc).
1739         limit(20)
1740     assert changesets.size <= 20
1741
1742     # Now check that all 20 (or however many were returned) changesets are in the html
1743     assert_select "li", :count => changesets.size
1744     changesets.each do |changeset|
1745       # FIXME this test needs rewriting - test for table contents
1746     end
1747   end
1748
1749   ##
1750   # This should display the last 20 changesets closed.
1751   def test_list_xhr
1752     xhr :get, :list, {:format => "html"}
1753     assert_response :success
1754     assert_template "history"
1755     assert_template :layout => "xhr"
1756     assert_select "h2", :text => "Changesets", :count => 1
1757
1758     get :list, {:format => "html", :list => '1', :bbox => '-180,-90,90,180'}
1759     assert_response :success
1760     assert_template "list"
1761
1762     changesets = Changeset.
1763         where("num_changes > 0 and min_lon is not null").
1764         order(:created_at => :desc).
1765         limit(20)
1766     assert changesets.size <= 20
1767
1768     # Now check that all 20 (or however many were returned) changesets are in the html
1769     assert_select "li", :count => changesets.size
1770     changesets.each do |changeset|
1771       # FIXME this test needs rewriting - test for table contents
1772     end
1773   end
1774
1775   ##
1776   # Checks the display of the user changesets listing
1777   def test_list_user
1778     user = users(:public_user)
1779     get :list, {:format => "html", :display_name => user.display_name}
1780     assert_response :success
1781     assert_template "history"
1782     ## FIXME need to add more checks to see which if edits are actually shown if your data is public
1783   end
1784
1785   ##
1786   # Check the not found of the list user changesets
1787   def test_list_user_not_found
1788     get :list, {:format => "html", :display_name => "Some random user"}
1789     assert_response :not_found
1790     assert_template 'user/no_such_user'
1791   end
1792
1793   ##
1794   # This should display the last 20 changesets closed.
1795   def test_feed
1796     changesets = Changeset.where("num_changes > 0").order(:created_at => :desc).limit(20)
1797     assert changesets.size <= 20
1798     get :feed, {:format => "atom"}
1799     assert_response :success
1800     assert_template "list"
1801     # Now check that all 20 (or however many were returned) changesets are in the html
1802     assert_select "feed", :count => 1
1803     assert_select "entry", :count => changesets.size
1804     changesets.each do |changeset|
1805       # FIXME this test needs rewriting - test for feed contents
1806     end
1807   end
1808
1809   ##
1810   # Checks the display of the user changesets feed
1811   def test_feed_user
1812     user = users(:public_user)
1813     get :feed, {:format => "atom", :display_name => user.display_name}
1814     assert_response :success
1815     assert_template "list"
1816     assert_equal "application/atom+xml", response.content_type
1817     ## FIXME need to add more checks to see which if edits are actually shown if your data is public
1818   end
1819
1820   ##
1821   # Check the not found of the user changesets feed
1822   def test_feed_user_not_found
1823     get :feed, {:format => "atom", :display_name => "Some random user"}
1824     assert_response :not_found
1825   end
1826
1827   ##
1828   # check that the changeset download for a changeset with a redacted
1829   # element in it doesn't contain that element.
1830   def test_diff_download_redacted
1831     changeset_id = changesets(:public_user_first_change).id
1832
1833     get :download, :id => changeset_id
1834     assert_response :success
1835
1836     assert_select "osmChange", 1
1837     # this changeset contains node 17 in versions 1 & 2, but 1 should
1838     # be hidden.
1839     assert_select "osmChange node[id=17]", 1
1840     assert_select "osmChange node[id=17][version=1]", 0
1841   end
1842
1843   #------------------------------------------------------------
1844   # utility functions
1845   #------------------------------------------------------------
1846
1847   ##
1848   # boilerplate for checking that certain changesets exist in the
1849   # output.
1850   def assert_changesets(ids)
1851     assert_select "osm>changeset", ids.size
1852     ids.each do |id|
1853       assert_select "osm>changeset[id=#{id}]", 1
1854     end
1855   end
1856
1857   ##
1858   # call the include method and assert properties of the bbox
1859   def check_after_include(changeset_id, lon, lat, bbox)
1860     content "<osm><node lon='#{lon}' lat='#{lat}'/></osm>"
1861     post :expand_bbox, :id => changeset_id
1862     assert_response :success, "Setting include of changeset failed: #{@response.body}"
1863
1864     # check exactly one changeset
1865     assert_select "osm>changeset", 1
1866     assert_select "osm>changeset[id=#{changeset_id}]", 1
1867
1868     # check the bbox
1869     doc = XML::Parser.string(@response.body).parse
1870     changeset = doc.find("//osm/changeset").first
1871     assert_equal bbox[0], changeset['min_lon'].to_f, "min lon"
1872     assert_equal bbox[1], changeset['min_lat'].to_f, "min lat"
1873     assert_equal bbox[2], changeset['max_lon'].to_f, "max lon"
1874     assert_equal bbox[3], changeset['max_lat'].to_f, "max lat"
1875   end
1876
1877   ##
1878   # update the changeset_id of a way element
1879   def update_changeset(xml, changeset_id)
1880     xml_attr_rewrite(xml, 'changeset', changeset_id)
1881   end
1882
1883   ##
1884   # update an attribute in a way element
1885   def xml_attr_rewrite(xml, name, value)
1886     xml.find("//osm/way").first[name] = value.to_s
1887     return xml
1888   end
1889
1890 end