]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets_controller_test.rb
Move more api-related helper methods to ApiController
[rails.git] / test / controllers / api / changesets_controller_test.rb
1 require "test_helper"
2
3 module Api
4   class ChangesetsControllerTest < ActionDispatch::IntegrationTest
5     ##
6     # test all routes which lead to this controller
7     def test_routes
8       assert_routing(
9         { :path => "/api/0.6/changeset/create", :method => :put },
10         { :controller => "api/changesets", :action => "create" }
11       )
12       assert_routing(
13         { :path => "/api/0.6/changeset/1/upload", :method => :post },
14         { :controller => "api/changesets", :action => "upload", :id => "1" }
15       )
16       assert_routing(
17         { :path => "/api/0.6/changeset/1/download", :method => :get },
18         { :controller => "api/changesets", :action => "download", :id => "1" }
19       )
20       assert_routing(
21         { :path => "/api/0.6/changeset/1", :method => :get },
22         { :controller => "api/changesets", :action => "show", :id => "1" }
23       )
24       assert_routing(
25         { :path => "/api/0.6/changeset/1.json", :method => :get },
26         { :controller => "api/changesets", :action => "show", :id => "1", :format => "json" }
27       )
28       assert_routing(
29         { :path => "/api/0.6/changeset/1/subscribe", :method => :post },
30         { :controller => "api/changesets", :action => "subscribe", :id => "1" }
31       )
32       assert_routing(
33         { :path => "/api/0.6/changeset/1/subscribe.json", :method => :post },
34         { :controller => "api/changesets", :action => "subscribe", :id => "1", :format => "json" }
35       )
36       assert_routing(
37         { :path => "/api/0.6/changeset/1/unsubscribe", :method => :post },
38         { :controller => "api/changesets", :action => "unsubscribe", :id => "1" }
39       )
40       assert_routing(
41         { :path => "/api/0.6/changeset/1/unsubscribe.json", :method => :post },
42         { :controller => "api/changesets", :action => "unsubscribe", :id => "1", :format => "json" }
43       )
44       assert_routing(
45         { :path => "/api/0.6/changeset/1", :method => :put },
46         { :controller => "api/changesets", :action => "update", :id => "1" }
47       )
48       assert_routing(
49         { :path => "/api/0.6/changeset/1/close", :method => :put },
50         { :controller => "api/changesets", :action => "close", :id => "1" }
51       )
52       assert_routing(
53         { :path => "/api/0.6/changesets", :method => :get },
54         { :controller => "api/changesets", :action => "query" }
55       )
56       assert_routing(
57         { :path => "/api/0.6/changesets.json", :method => :get },
58         { :controller => "api/changesets", :action => "query", :format => "json" }
59       )
60     end
61
62     # -----------------------
63     # Test simple changeset creation
64     # -----------------------
65
66     def test_create
67       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
68       # Create the first user's changeset
69       xml = "<osm><changeset>" \
70             "<tag k='created_by' v='osm test suite checking changesets'/>" \
71             "</changeset></osm>"
72       put changeset_create_path, :params => xml, :headers => auth_header
73       assert_require_public_data
74
75       auth_header = basic_authorization_header create(:user).email, "test"
76       # Create the first user's changeset
77       xml = "<osm><changeset>" \
78             "<tag k='created_by' v='osm test suite checking changesets'/>" \
79             "</changeset></osm>"
80       put changeset_create_path, :params => xml, :headers => auth_header
81
82       assert_response :success, "Creation of changeset did not return success status"
83       newid = @response.body.to_i
84
85       # check end time, should be an hour ahead of creation time
86       cs = Changeset.find(newid)
87       duration = cs.closed_at - cs.created_at
88       # the difference can either be a rational, or a floating point number
89       # of seconds, depending on the code path taken :-(
90       if duration.instance_of?(Rational)
91         assert_equal Rational(1, 24), duration, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
92       else
93         # must be number of seconds...
94         assert_equal 3600, duration.round, "initial idle timeout should be an hour (#{cs.created_at} -> #{cs.closed_at})"
95       end
96
97       # checks if uploader was subscribed
98       assert_equal 1, cs.subscribers.length
99     end
100
101     def test_create_invalid
102       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
103       xml = "<osm><changeset></osm>"
104       put changeset_create_path, :params => xml, :headers => auth_header
105       assert_require_public_data
106
107       ## Try the public user
108       auth_header = basic_authorization_header create(:user).email, "test"
109       xml = "<osm><changeset></osm>"
110       put changeset_create_path, :params => xml, :headers => auth_header
111       assert_response :bad_request, "creating a invalid changeset should fail"
112     end
113
114     def test_create_invalid_no_content
115       ## First check with no auth
116       put changeset_create_path
117       assert_response :unauthorized, "shouldn't be able to create a changeset with no auth"
118
119       ## Now try to with a non-public user
120       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
121       put changeset_create_path, :headers => auth_header
122       assert_require_public_data
123
124       ## Try an inactive user
125       auth_header = basic_authorization_header create(:user, :pending).email, "test"
126       put changeset_create_path, :headers => auth_header
127       assert_inactive_user
128
129       ## Now try to use a normal user
130       auth_header = basic_authorization_header create(:user).email, "test"
131       put changeset_create_path, :headers => auth_header
132       assert_response :bad_request, "creating a changeset with no content should fail"
133     end
134
135     def test_create_wrong_method
136       auth_header = basic_authorization_header create(:user).email, "test"
137       assert_raise ActionController::RoutingError do
138         get changeset_create_path, :headers => auth_header
139       end
140       assert_raise ActionController::RoutingError do
141         post changeset_create_path, :headers => auth_header
142       end
143     end
144
145     ##
146     # check that the changeset can be shown and returns the correct
147     # document structure.
148     def test_show
149       changeset = create(:changeset)
150
151       get changeset_show_path(changeset)
152       assert_response :success, "cannot get first changeset"
153
154       assert_select "osm[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
155       assert_select "osm>changeset[id='#{changeset.id}']", 1
156       assert_select "osm>changeset>@open", "true"
157       assert_select "osm>changeset>@created_at", changeset.created_at.xmlschema
158       assert_select "osm>changeset>@closed_at", 0
159       assert_select "osm>changeset>discussion", 0
160
161       get changeset_show_path(changeset), :params => { :include_discussion => true }
162       assert_response :success, "cannot get first changeset with comments"
163
164       assert_select "osm[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
165       assert_select "osm>changeset[id='#{changeset.id}']", 1
166       assert_select "osm>changeset>@open", "true"
167       assert_select "osm>changeset>@created_at", changeset.created_at.xmlschema
168       assert_select "osm>changeset>@closed_at", 0
169       assert_select "osm>changeset>discussion", 1
170       assert_select "osm>changeset>discussion>comment", 0
171
172       changeset = create(:changeset, :closed)
173       create_list(:changeset_comment, 3, :changeset_id => changeset.id)
174
175       get changeset_show_path(changeset), :params => { :include_discussion => true }
176       assert_response :success, "cannot get closed changeset with comments"
177
178       assert_select "osm[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
179       assert_select "osm>changeset[id='#{changeset.id}']", 1
180       assert_select "osm>changeset>@open", "false"
181       assert_select "osm>changeset>@created_at", changeset.created_at.xmlschema
182       assert_select "osm>changeset>@closed_at", changeset.closed_at.xmlschema
183       assert_select "osm>changeset>discussion", 1
184       assert_select "osm>changeset>discussion>comment", 3
185     end
186
187     def test_show_json
188       changeset = create(:changeset)
189
190       get changeset_show_path(changeset), :params => { :format => "json" }
191       assert_response :success, "cannot get first changeset"
192
193       js = ActiveSupport::JSON.decode(@response.body)
194       assert_not_nil js
195
196       assert_equal Settings.api_version, js["version"]
197       assert_equal "OpenStreetMap server", js["generator"]
198       assert_equal changeset.id, js["changeset"]["id"]
199       assert js["changeset"]["open"]
200       assert_equal changeset.created_at.xmlschema, js["changeset"]["created_at"]
201       assert_nil js["changeset"]["closed_at"]
202       assert_nil js["changeset"]["tags"]
203       assert_nil js["changeset"]["comments"]
204       assert_equal changeset.user.id, js["changeset"]["uid"]
205       assert_equal changeset.user.display_name, js["changeset"]["user"]
206
207       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true }
208       assert_response :success, "cannot get first changeset with comments"
209
210       js = ActiveSupport::JSON.decode(@response.body)
211       assert_not_nil js
212       assert_equal Settings.api_version, js["version"]
213       assert_equal "OpenStreetMap server", js["generator"]
214       assert_equal changeset.id, js["changeset"]["id"]
215       assert js["changeset"]["open"]
216       assert_equal changeset.created_at.xmlschema, js["changeset"]["created_at"]
217       assert_nil js["changeset"]["closed_at"]
218       assert_nil js["changeset"]["tags"]
219       assert_nil js["changeset"]["min_lat"]
220       assert_nil js["changeset"]["min_lon"]
221       assert_nil js["changeset"]["max_lat"]
222       assert_nil js["changeset"]["max_lon"]
223       assert_equal 0, js["changeset"]["comments"].count
224     end
225
226     def test_show_tag_and_discussion_json
227       changeset = create(:changeset, :closed)
228
229       tag1 = ChangesetTag.new
230       tag1.changeset_id = changeset.id
231       tag1.k = "created_by"
232       tag1.v = "JOSM/1.5 (18364)"
233
234       tag2 = ChangesetTag.new
235       tag2.changeset_id = changeset.id
236       tag2.k = "comment"
237       tag2.v = "changeset comment"
238
239       changeset.changeset_tags = [tag1, tag2]
240
241       create_list(:changeset_comment, 3, :changeset_id => changeset.id)
242
243       get changeset_show_path(changeset), :params => { :format => "json", :include_discussion => true }
244       assert_response :success, "cannot get closed changeset with comments"
245
246       js = ActiveSupport::JSON.decode(@response.body)
247
248       assert_not_nil js
249       assert_equal Settings.api_version, js["version"]
250       assert_equal "OpenStreetMap server", js["generator"]
251       assert_equal changeset.id, js["changeset"]["id"]
252       assert_not js["changeset"]["open"]
253       assert_equal changeset.created_at.xmlschema, js["changeset"]["created_at"]
254       assert_equal changeset.closed_at.xmlschema, js["changeset"]["closed_at"]
255       assert_equal 2, js["changeset"]["tags"].count
256       assert_equal 3, js["changeset"]["comments"].count
257       assert_equal 3, js["changeset"]["comments_count"]
258       assert_equal 0, js["changeset"]["changes_count"]
259       assert_not_nil js["changeset"]["comments"][0]["uid"]
260       assert_not_nil js["changeset"]["comments"][0]["user"]
261       assert_not_nil js["changeset"]["comments"][0]["text"]
262     end
263
264     def test_show_bbox_json
265       # test bbox attribute
266       changeset = create(:changeset, :min_lat => (-5 * GeoRecord::SCALE).round, :min_lon => (5 * GeoRecord::SCALE).round,
267                                      :max_lat => (15 * GeoRecord::SCALE).round, :max_lon => (12 * GeoRecord::SCALE).round)
268
269       get changeset_show_path(changeset), :params => { :format => "json" }
270       assert_response :success, "cannot get first changeset"
271
272       js = ActiveSupport::JSON.decode(@response.body)
273       assert_not_nil js
274       assert_equal(-5, js["changeset"]["min_lat"])
275       assert_equal  5, js["changeset"]["min_lon"]
276       assert_equal 15, js["changeset"]["max_lat"]
277       assert_equal 12, js["changeset"]["max_lon"]
278     end
279
280     ##
281     # check that a changeset that doesn't exist returns an appropriate message
282     def test_show_not_found
283       [0, -32, 233455644, "afg", "213"].each do |id|
284         get changeset_show_path(:id => id)
285         assert_response :not_found, "should get a not found"
286       rescue ActionController::UrlGenerationError => e
287         assert_match(/No route matches/, e.to_s)
288       end
289     end
290
291     ##
292     # test that the user who opened a change can close it
293     def test_close
294       private_user = create(:user, :data_public => false)
295       private_changeset = create(:changeset, :user => private_user)
296       user = create(:user)
297       changeset = create(:changeset, :user => user)
298
299       ## Try without authentication
300       put changeset_close_path(changeset)
301       assert_response :unauthorized
302
303       ## Try using the non-public user
304       auth_header = basic_authorization_header private_user.email, "test"
305       put changeset_close_path(private_changeset), :headers => auth_header
306       assert_require_public_data
307
308       ## The try with the public user
309       auth_header = basic_authorization_header user.email, "test"
310
311       cs_id = changeset.id
312       put changeset_close_path(:id => cs_id), :headers => auth_header
313       assert_response :success
314
315       # test that it really is closed now
316       cs = Changeset.find(changeset.id)
317       assert_not(cs.open?,
318                  "changeset should be closed now (#{cs.closed_at} > #{Time.now.utc}.")
319     end
320
321     ##
322     # test that a different user can't close another user's changeset
323     def test_close_invalid
324       user = create(:user)
325       changeset = create(:changeset)
326
327       auth_header = basic_authorization_header user.email, "test"
328
329       put changeset_close_path(changeset), :headers => auth_header
330       assert_response :conflict
331       assert_equal "The user doesn't own that changeset", @response.body
332     end
333
334     ##
335     # test that you can't close using another method
336     def test_close_method_invalid
337       user = create(:user)
338       changeset = create(:changeset, :user => user)
339
340       auth_header = basic_authorization_header user.email, "test"
341
342       assert_raise ActionController::RoutingError do
343         get changeset_close_path(changeset), :headers => auth_header
344       end
345
346       assert_raise ActionController::RoutingError do
347         post changeset_close_path(changeset), :headers => auth_header
348       end
349     end
350
351     ##
352     # check that you can't close a changeset that isn't found
353     def test_close_not_found
354       cs_ids = [0, -132, "123"]
355
356       # First try to do it with no auth
357       cs_ids.each do |id|
358         put changeset_close_path(:id => id)
359         assert_response :unauthorized, "Shouldn't be able close the non-existant changeset #{id}, when not authorized"
360       rescue ActionController::UrlGenerationError => e
361         assert_match(/No route matches/, e.to_s)
362       end
363
364       # Now try with auth
365       auth_header = basic_authorization_header create(:user).email, "test"
366       cs_ids.each do |id|
367         put changeset_close_path(:id => id), :headers => auth_header
368         assert_response :not_found, "The changeset #{id} doesn't exist, so can't be closed"
369       rescue ActionController::UrlGenerationError => e
370         assert_match(/No route matches/, e.to_s)
371       end
372     end
373
374     ##
375     # upload something simple, but valid and check that it can
376     # be read back ok
377     # Also try without auth and another user.
378     def test_upload_simple_valid
379       private_user = create(:user, :data_public => false)
380       private_changeset = create(:changeset, :user => private_user)
381       user = create(:user)
382       changeset = create(:changeset, :user => user)
383
384       node = create(:node)
385       way = create(:way)
386       relation = create(:relation)
387       other_relation = create(:relation)
388       # create some tags, since we test that they are removed later
389       create(:node_tag, :node => node)
390       create(:way_tag, :way => way)
391       create(:relation_tag, :relation => relation)
392
393       ## Try with no auth
394       changeset_id = changeset.id
395
396       # simple diff to change a node, way and relation by removing
397       # their tags
398       diff = <<~CHANGESET
399         <osmChange>
400          <modify>
401           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
402           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
403            <nd ref='#{node.id}'/>
404           </way>
405          </modify>
406          <modify>
407           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
408            <member type='way' role='some' ref='#{way.id}'/>
409            <member type='node' role='some' ref='#{node.id}'/>
410            <member type='relation' role='some' ref='#{other_relation.id}'/>
411           </relation>
412          </modify>
413         </osmChange>
414       CHANGESET
415
416       # upload it
417       post changeset_upload_path(changeset), :params => diff
418       assert_response :unauthorized,
419                       "shouldn't be able to upload a simple valid diff to changeset: #{@response.body}"
420
421       ## Now try with a private user
422       auth_header = basic_authorization_header private_user.email, "test"
423       changeset_id = private_changeset.id
424
425       # simple diff to change a node, way and relation by removing
426       # their tags
427       diff = <<~CHANGESET
428         <osmChange>
429          <modify>
430           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
431           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
432            <nd ref='#{node.id}'/>
433           </way>
434          </modify>
435          <modify>
436           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
437            <member type='way' role='some' ref='#{way.id}'/>
438            <member type='node' role='some' ref='#{node.id}'/>
439            <member type='relation' role='some' ref='#{other_relation.id}'/>
440           </relation>
441          </modify>
442         </osmChange>
443       CHANGESET
444
445       # upload it
446       post changeset_upload_path(private_changeset), :params => diff, :headers => auth_header
447       assert_response :forbidden,
448                       "can't upload a simple valid diff to changeset: #{@response.body}"
449
450       ## Now try with the public user
451       auth_header = basic_authorization_header user.email, "test"
452       changeset_id = changeset.id
453
454       # simple diff to change a node, way and relation by removing
455       # their tags
456       diff = <<~CHANGESET
457         <osmChange>
458          <modify>
459           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
460           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
461            <nd ref='#{node.id}'/>
462           </way>
463          </modify>
464          <modify>
465           <relation id='#{relation.id}' changeset='#{changeset_id}' version='1'>
466            <member type='way' role='some' ref='#{way.id}'/>
467            <member type='node' role='some' ref='#{node.id}'/>
468            <member type='relation' role='some' ref='#{other_relation.id}'/>
469           </relation>
470          </modify>
471         </osmChange>
472       CHANGESET
473
474       # upload it
475       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
476       assert_response :success,
477                       "can't upload a simple valid diff to changeset: #{@response.body}"
478
479       # check that the changes made it into the database
480       assert_equal 0, Node.find(node.id).tags.size, "node #{node.id} should now have no tags"
481       assert_equal 0, Way.find(way.id).tags.size, "way #{way.id} should now have no tags"
482       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
483     end
484
485     ##
486     # upload something which creates new objects using placeholders
487     def test_upload_create_valid
488       user = create(:user)
489       changeset = create(:changeset, :user => user)
490       node = create(:node)
491       way = create(:way_with_nodes, :nodes_count => 2)
492       relation = create(:relation)
493
494       auth_header = basic_authorization_header user.email, "test"
495
496       # simple diff to create a node way and relation using placeholders
497       diff = <<~CHANGESET
498         <osmChange>
499          <create>
500           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
501            <tag k='foo' v='bar'/>
502            <tag k='baz' v='bat'/>
503           </node>
504           <way id='-1' changeset='#{changeset.id}'>
505            <nd ref='#{node.id}'/>
506           </way>
507          </create>
508          <create>
509           <relation id='-1' changeset='#{changeset.id}'>
510            <member type='way' role='some' ref='#{way.id}'/>
511            <member type='node' role='some' ref='#{node.id}'/>
512            <member type='relation' role='some' ref='#{relation.id}'/>
513           </relation>
514          </create>
515         </osmChange>
516       CHANGESET
517
518       # upload it
519       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
520       assert_response :success,
521                       "can't upload a simple valid creation to changeset: #{@response.body}"
522
523       # check the returned payload
524       assert_select "diffResult[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
525       assert_select "diffResult>node", 1
526       assert_select "diffResult>way", 1
527       assert_select "diffResult>relation", 1
528
529       # inspect the response to find out what the new element IDs are
530       doc = XML::Parser.string(@response.body).parse
531       new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
532       new_way_id = doc.find("//diffResult/way").first["new_id"].to_i
533       new_rel_id = doc.find("//diffResult/relation").first["new_id"].to_i
534
535       # check the old IDs are all present and negative one
536       assert_equal(-1, doc.find("//diffResult/node").first["old_id"].to_i)
537       assert_equal(-1, doc.find("//diffResult/way").first["old_id"].to_i)
538       assert_equal(-1, doc.find("//diffResult/relation").first["old_id"].to_i)
539
540       # check the versions are present and equal one
541       assert_equal 1, doc.find("//diffResult/node").first["new_version"].to_i
542       assert_equal 1, doc.find("//diffResult/way").first["new_version"].to_i
543       assert_equal 1, doc.find("//diffResult/relation").first["new_version"].to_i
544
545       # check that the changes made it into the database
546       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
547       assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
548       assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
549     end
550
551     ##
552     # test a complex delete where we delete elements which rely on eachother
553     # in the same transaction.
554     def test_upload_delete
555       changeset = create(:changeset)
556       super_relation = create(:relation)
557       used_relation = create(:relation)
558       used_way = create(:way)
559       used_node = create(:node)
560       create(:relation_member, :relation => super_relation, :member => used_relation)
561       create(:relation_member, :relation => super_relation, :member => used_way)
562       create(:relation_member, :relation => super_relation, :member => used_node)
563
564       auth_header = basic_authorization_header changeset.user.display_name, "test"
565
566       diff = XML::Document.new
567       diff.root = XML::Node.new "osmChange"
568       delete = XML::Node.new "delete"
569       diff.root << delete
570       delete << xml_node_for_relation(super_relation)
571       delete << xml_node_for_relation(used_relation)
572       delete << xml_node_for_way(used_way)
573       delete << xml_node_for_node(used_node)
574
575       # update the changeset to one that this user owns
576       %w[node way relation].each do |type|
577         delete.find("//osmChange/delete/#{type}").each do |n|
578           n["changeset"] = changeset.id.to_s
579         end
580       end
581
582       # upload it
583       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
584       assert_response :success,
585                       "can't upload a deletion diff to changeset: #{@response.body}"
586
587       # check the response is well-formed
588       assert_select "diffResult>node", 1
589       assert_select "diffResult>way", 1
590       assert_select "diffResult>relation", 2
591
592       # check that everything was deleted
593       assert_not Node.find(used_node.id).visible
594       assert_not Way.find(used_way.id).visible
595       assert_not Relation.find(super_relation.id).visible
596       assert_not Relation.find(used_relation.id).visible
597     end
598
599     ##
600     # test uploading a delete with no lat/lon, as they are optional in
601     # the osmChange spec.
602     def test_upload_nolatlon_delete
603       node = create(:node)
604       changeset = create(:changeset)
605
606       auth_header = basic_authorization_header changeset.user.display_name, "test"
607       diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
608
609       # upload it
610       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
611       assert_response :success,
612                       "can't upload a deletion diff to changeset: #{@response.body}"
613
614       # check the response is well-formed
615       assert_select "diffResult>node", 1
616
617       # check that everything was deleted
618       assert_not Node.find(node.id).visible
619     end
620
621     def test_repeated_changeset_create
622       3.times do
623         auth_header = basic_authorization_header create(:user).email, "test"
624
625         # create a temporary changeset
626         xml = "<osm><changeset>" \
627               "<tag k='created_by' v='osm test suite checking changesets'/>" \
628               "</changeset></osm>"
629         assert_difference "Changeset.count", 1 do
630           put changeset_create_path, :params => xml, :headers => auth_header
631         end
632         assert_response :success
633       end
634     end
635
636     def test_upload_large_changeset
637       auth_header = basic_authorization_header create(:user).email, "test"
638
639       # create a changeset
640       put changeset_create_path, :params => "<osm><changeset/></osm>", :headers => auth_header
641       assert_response :success, "Should be able to create a changeset: #{@response.body}"
642       changeset_id = @response.body.to_i
643
644       # upload some widely-spaced nodes, spiralling positive and negative
645       diff = <<~CHANGESET
646         <osmChange>
647          <create>
648           <node id='-1' lon='-20' lat='-10' changeset='#{changeset_id}'/>
649           <node id='-10' lon='20'  lat='10' changeset='#{changeset_id}'/>
650           <node id='-2' lon='-40' lat='-20' changeset='#{changeset_id}'/>
651           <node id='-11' lon='40'  lat='20' changeset='#{changeset_id}'/>
652           <node id='-3' lon='-60' lat='-30' changeset='#{changeset_id}'/>
653           <node id='-12' lon='60'  lat='30' changeset='#{changeset_id}'/>
654           <node id='-4' lon='-80' lat='-40' changeset='#{changeset_id}'/>
655           <node id='-13' lon='80'  lat='40' changeset='#{changeset_id}'/>
656           <node id='-5' lon='-100' lat='-50' changeset='#{changeset_id}'/>
657           <node id='-14' lon='100'  lat='50' changeset='#{changeset_id}'/>
658           <node id='-6' lon='-120' lat='-60' changeset='#{changeset_id}'/>
659           <node id='-15' lon='120'  lat='60' changeset='#{changeset_id}'/>
660           <node id='-7' lon='-140' lat='-70' changeset='#{changeset_id}'/>
661           <node id='-16' lon='140'  lat='70' changeset='#{changeset_id}'/>
662           <node id='-8' lon='-160' lat='-80' changeset='#{changeset_id}'/>
663           <node id='-17' lon='160'  lat='80' changeset='#{changeset_id}'/>
664           <node id='-9' lon='-179.9' lat='-89.9' changeset='#{changeset_id}'/>
665           <node id='-18' lon='179.9'  lat='89.9' changeset='#{changeset_id}'/>
666          </create>
667         </osmChange>
668       CHANGESET
669
670       # upload it, which used to cause an error like "PGError: ERROR:
671       # integer out of range" (bug #2152). but shouldn't any more.
672       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
673       assert_response :success,
674                       "can't upload a spatially-large diff to changeset: #{@response.body}"
675
676       # check that the changeset bbox is within bounds
677       cs = Changeset.find(changeset_id)
678       assert cs.min_lon >= -180 * GeoRecord::SCALE, "Minimum longitude (#{cs.min_lon / GeoRecord::SCALE}) should be >= -180 to be valid."
679       assert cs.max_lon <= 180 * GeoRecord::SCALE, "Maximum longitude (#{cs.max_lon / GeoRecord::SCALE}) should be <= 180 to be valid."
680       assert cs.min_lat >= -90 * GeoRecord::SCALE, "Minimum latitude (#{cs.min_lat / GeoRecord::SCALE}) should be >= -90 to be valid."
681       assert cs.max_lat <= 90 * GeoRecord::SCALE, "Maximum latitude (#{cs.max_lat / GeoRecord::SCALE}) should be <= 90 to be valid."
682     end
683
684     ##
685     # test that deleting stuff in a transaction doesn't bypass the checks
686     # to ensure that used elements are not deleted.
687     def test_upload_delete_invalid
688       changeset = create(:changeset)
689       relation = create(:relation)
690       other_relation = create(:relation)
691       used_way = create(:way)
692       used_node = create(:node)
693       create(:relation_member, :relation => relation, :member => used_way)
694       create(:relation_member, :relation => relation, :member => used_node)
695
696       auth_header = basic_authorization_header changeset.user.email, "test"
697
698       diff = XML::Document.new
699       diff.root = XML::Node.new "osmChange"
700       delete = XML::Node.new "delete"
701       diff.root << delete
702       delete << xml_node_for_relation(other_relation)
703       delete << xml_node_for_way(used_way)
704       delete << xml_node_for_node(used_node)
705
706       # update the changeset to one that this user owns
707       %w[node way relation].each do |type|
708         delete.find("//osmChange/delete/#{type}").each do |n|
709           n["changeset"] = changeset.id.to_s
710         end
711       end
712
713       # upload it
714       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
715       assert_response :precondition_failed,
716                       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
717       assert_equal "Precondition failed: Way #{used_way.id} is still used by relations #{relation.id}.", @response.body
718
719       # check that nothing was, in fact, deleted
720       assert Node.find(used_node.id).visible
721       assert Way.find(used_way.id).visible
722       assert Relation.find(relation.id).visible
723       assert Relation.find(other_relation.id).visible
724     end
725
726     ##
727     # test that a conditional delete of an in use object works.
728     def test_upload_delete_if_unused
729       changeset = create(:changeset)
730       super_relation = create(:relation)
731       used_relation = create(:relation)
732       used_way = create(:way)
733       used_node = create(:node)
734       create(:relation_member, :relation => super_relation, :member => used_relation)
735       create(:relation_member, :relation => super_relation, :member => used_way)
736       create(:relation_member, :relation => super_relation, :member => used_node)
737
738       auth_header = basic_authorization_header changeset.user.email, "test"
739
740       diff = XML::Document.new
741       diff.root = XML::Node.new "osmChange"
742       delete = XML::Node.new "delete"
743       diff.root << delete
744       delete["if-unused"] = ""
745       delete << xml_node_for_relation(used_relation)
746       delete << xml_node_for_way(used_way)
747       delete << xml_node_for_node(used_node)
748
749       # update the changeset to one that this user owns
750       %w[node way relation].each do |type|
751         delete.find("//osmChange/delete/#{type}").each do |n|
752           n["changeset"] = changeset.id.to_s
753         end
754       end
755
756       # upload it
757       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
758       assert_response :success,
759                       "can't do a conditional delete of in use objects: #{@response.body}"
760
761       # check the returned payload
762       assert_select "diffResult[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
763       assert_select "diffResult>node", 1
764       assert_select "diffResult>way", 1
765       assert_select "diffResult>relation", 1
766
767       # parse the response
768       doc = XML::Parser.string(@response.body).parse
769
770       # check the old IDs are all present and what we expect
771       assert_equal used_node.id, doc.find("//diffResult/node").first["old_id"].to_i
772       assert_equal used_way.id, doc.find("//diffResult/way").first["old_id"].to_i
773       assert_equal used_relation.id, doc.find("//diffResult/relation").first["old_id"].to_i
774
775       # check the new IDs are all present and unchanged
776       assert_equal used_node.id, doc.find("//diffResult/node").first["new_id"].to_i
777       assert_equal used_way.id, doc.find("//diffResult/way").first["new_id"].to_i
778       assert_equal used_relation.id, doc.find("//diffResult/relation").first["new_id"].to_i
779
780       # check the new versions are all present and unchanged
781       assert_equal used_node.version, doc.find("//diffResult/node").first["new_version"].to_i
782       assert_equal used_way.version, doc.find("//diffResult/way").first["new_version"].to_i
783       assert_equal used_relation.version, doc.find("//diffResult/relation").first["new_version"].to_i
784
785       # check that nothing was, in fact, deleted
786       assert Node.find(used_node.id).visible
787       assert Way.find(used_way.id).visible
788       assert Relation.find(used_relation.id).visible
789     end
790
791     ##
792     # upload an element with a really long tag value
793     def test_upload_invalid_too_long_tag
794       changeset = create(:changeset)
795
796       auth_header = basic_authorization_header changeset.user.email, "test"
797
798       # simple diff to create a node way and relation using placeholders
799       diff = <<~CHANGESET
800         <osmChange>
801          <create>
802           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
803            <tag k='foo' v='#{'x' * 256}'/>
804           </node>
805          </create>
806         </osmChange>
807       CHANGESET
808
809       # upload it
810       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
811       assert_response :bad_request,
812                       "shouldn't be able to upload too long a tag to changeset: #{@response.body}"
813     end
814
815     ##
816     # upload something which creates new objects and inserts them into
817     # existing containers using placeholders.
818     def test_upload_complex
819       way = create(:way)
820       node = create(:node)
821       relation = create(:relation)
822       create(:way_node, :way => way, :node => node)
823
824       changeset = create(:changeset)
825
826       auth_header = basic_authorization_header changeset.user.email, "test"
827
828       # simple diff to create a node way and relation using placeholders
829       diff = <<~CHANGESET
830         <osmChange>
831          <create>
832           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
833            <tag k='foo' v='bar'/>
834            <tag k='baz' v='bat'/>
835           </node>
836          </create>
837          <modify>
838           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
839            <nd ref='-1'/>
840            <nd ref='#{node.id}'/>
841           </way>
842           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
843            <member type='way' role='some' ref='#{way.id}'/>
844            <member type='node' role='some' ref='-1'/>
845            <member type='relation' role='some' ref='#{relation.id}'/>
846           </relation>
847          </modify>
848         </osmChange>
849       CHANGESET
850
851       # upload it
852       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
853       assert_response :success,
854                       "can't upload a complex diff to changeset: #{@response.body}"
855
856       # check the returned payload
857       assert_select "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
858       assert_select "diffResult>node", 1
859       assert_select "diffResult>way", 1
860       assert_select "diffResult>relation", 1
861
862       # inspect the response to find out what the new element IDs are
863       doc = XML::Parser.string(@response.body).parse
864       new_node_id = doc.find("//diffResult/node").first["new_id"].to_i
865
866       # check that the changes made it into the database
867       assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
868       assert_equal [new_node_id, node.id], Way.find(way.id).nds, "way nodes should match"
869       Relation.find(relation.id).members.each do |type, id, _role|
870         assert_equal new_node_id, id, "relation should contain new node" if type == "node"
871       end
872     end
873
874     ##
875     # create a diff which references several changesets, which should cause
876     # a rollback and none of the diff gets committed
877     def test_upload_invalid_changesets
878       changeset = create(:changeset)
879       other_changeset = create(:changeset, :user => changeset.user)
880       node = create(:node)
881       way = create(:way)
882       relation = create(:relation)
883       other_relation = create(:relation)
884
885       auth_header = basic_authorization_header changeset.user.email, "test"
886
887       # simple diff to create a node way and relation using placeholders
888       diff = <<~CHANGESET
889         <osmChange>
890          <modify>
891           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
892           <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
893            <nd ref='#{node.id}'/>
894           </way>
895          </modify>
896          <modify>
897           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
898            <member type='way' role='some' ref='#{way.id}'/>
899            <member type='node' role='some' ref='#{node.id}'/>
900            <member type='relation' role='some' ref='#{other_relation.id}'/>
901           </relation>
902          </modify>
903          <create>
904           <node id='-1' lon='0' lat='0' changeset='#{other_changeset.id}'>
905            <tag k='foo' v='bar'/>
906            <tag k='baz' v='bat'/>
907           </node>
908          </create>
909         </osmChange>
910       CHANGESET
911
912       # upload it
913       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
914       assert_response :conflict,
915                       "uploading a diff with multiple changesets should have failed"
916
917       # check that objects are unmodified
918       assert_nodes_are_equal(node, Node.find(node.id))
919       assert_ways_are_equal(way, Way.find(way.id))
920       assert_relations_are_equal(relation, Relation.find(relation.id))
921     end
922
923     ##
924     # upload multiple versions of the same element in the same diff.
925     def test_upload_multiple_valid
926       node = create(:node)
927       changeset = create(:changeset)
928       auth_header = basic_authorization_header changeset.user.email, "test"
929
930       # change the location of a node multiple times, each time referencing
931       # the last version. doesn't this depend on version numbers being
932       # sequential?
933       diff = <<~CHANGESET
934         <osmChange>
935          <modify>
936           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
937           <node id='#{node.id}' lon='1' lat='0' changeset='#{changeset.id}' version='2'/>
938           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='3'/>
939           <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='4'/>
940           <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset.id}' version='5'/>
941           <node id='#{node.id}' lon='3' lat='2' changeset='#{changeset.id}' version='6'/>
942           <node id='#{node.id}' lon='3' lat='3' changeset='#{changeset.id}' version='7'/>
943           <node id='#{node.id}' lon='9' lat='9' changeset='#{changeset.id}' version='8'/>
944          </modify>
945         </osmChange>
946       CHANGESET
947
948       # upload it
949       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
950       assert_response :success,
951                       "can't upload multiple versions of an element in a diff: #{@response.body}"
952
953       # check the response is well-formed. its counter-intuitive, but the
954       # API will return multiple elements with the same ID and different
955       # version numbers for each change we made.
956       assert_select "diffResult>node", 8
957     end
958
959     ##
960     # upload multiple versions of the same element in the same diff, but
961     # keep the version numbers the same.
962     def test_upload_multiple_duplicate
963       node = create(:node)
964       changeset = create(:changeset)
965
966       auth_header = basic_authorization_header changeset.user.email, "test"
967
968       diff = <<~CHANGESET
969         <osmChange>
970          <modify>
971           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
972           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
973          </modify>
974         </osmChange>
975       CHANGESET
976
977       # upload it
978       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
979       assert_response :conflict,
980                       "shouldn't be able to upload the same element twice in a diff: #{@response.body}"
981     end
982
983     ##
984     # try to upload some elements without specifying the version
985     def test_upload_missing_version
986       changeset = create(:changeset)
987
988       auth_header = basic_authorization_header changeset.user.email, "test"
989
990       diff = <<~CHANGESET
991         <osmChange>
992          <modify>
993          <node id='1' lon='1' lat='1' changeset='#{changeset.id}'/>
994          </modify>
995         </osmChange>
996       CHANGESET
997
998       # upload it
999       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1000       assert_response :bad_request,
1001                       "shouldn't be able to upload an element without version: #{@response.body}"
1002     end
1003
1004     ##
1005     # try to upload with commands other than create, modify, or delete
1006     def test_action_upload_invalid
1007       changeset = create(:changeset)
1008
1009       auth_header = basic_authorization_header changeset.user.email, "test"
1010
1011       diff = <<~CHANGESET
1012         <osmChange>
1013           <ping>
1014            <node id='1' lon='1' lat='1' changeset='#{changeset.id}' />
1015           </ping>
1016         </osmChange>
1017       CHANGESET
1018       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1019       assert_response :bad_request, "Shouldn't be able to upload a diff with the action ping"
1020       assert_equal("Unknown action ping, choices are create, modify, delete", @response.body)
1021     end
1022
1023     ##
1024     # upload a valid changeset which has a mixture of whitespace
1025     # to check a bug reported by ivansanchez (#1565).
1026     def test_upload_whitespace_valid
1027       changeset = create(:changeset)
1028       node = create(:node)
1029       way = create(:way_with_nodes, :nodes_count => 2)
1030       relation = create(:relation)
1031       other_relation = create(:relation)
1032       create(:relation_tag, :relation => relation)
1033
1034       auth_header = basic_authorization_header changeset.user.email, "test"
1035
1036       diff = <<~CHANGESET
1037         <osmChange>
1038          <modify><node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}'
1039           version='1'></node>
1040           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='2'><tag k='k' v='v'/></node></modify>
1041          <modify>
1042          <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'><member
1043            type='way' role='some' ref='#{way.id}'/><member
1044             type='node' role='some' ref='#{node.id}'/>
1045            <member type='relation' role='some' ref='#{other_relation.id}'/>
1046           </relation>
1047          </modify></osmChange>
1048       CHANGESET
1049
1050       # upload it
1051       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1052       assert_response :success,
1053                       "can't upload a valid diff with whitespace variations to changeset: #{@response.body}"
1054
1055       # check the response is well-formed
1056       assert_select "diffResult>node", 2
1057       assert_select "diffResult>relation", 1
1058
1059       # check that the changes made it into the database
1060       assert_equal 1, Node.find(node.id).tags.size, "node #{node.id} should now have one tag"
1061       assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
1062     end
1063
1064     ##
1065     # test that a placeholder can be reused within the same upload.
1066     def test_upload_reuse_placeholder_valid
1067       changeset = create(:changeset)
1068
1069       auth_header = basic_authorization_header changeset.user.email, "test"
1070
1071       diff = <<~CHANGESET
1072         <osmChange>
1073          <create>
1074           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1075            <tag k="foo" v="bar"/>
1076           </node>
1077          </create>
1078          <modify>
1079           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1080          </modify>
1081          <delete>
1082           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
1083          </delete>
1084         </osmChange>
1085       CHANGESET
1086
1087       # upload it
1088       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1089       assert_response :success,
1090                       "can't upload a valid diff with re-used placeholders to changeset: #{@response.body}"
1091
1092       # check the response is well-formed
1093       assert_select "diffResult>node", 3
1094       assert_select "diffResult>node[old_id='-1']", 3
1095     end
1096
1097     ##
1098     # test what happens if a diff upload re-uses placeholder IDs in an
1099     # illegal way.
1100     def test_upload_placeholder_invalid
1101       changeset = create(:changeset)
1102
1103       auth_header = basic_authorization_header changeset.user.email, "test"
1104
1105       diff = <<~CHANGESET
1106         <osmChange>
1107          <create>
1108           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1109           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1110           <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
1111          </create>
1112         </osmChange>
1113       CHANGESET
1114
1115       # upload it
1116       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1117       assert_response :bad_request,
1118                       "shouldn't be able to re-use placeholder IDs"
1119
1120       # placeholder_ids must be unique across all action blocks
1121       diff = <<~CHANGESET
1122         <osmChange>
1123          <create>
1124           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1125          </create>
1126          <create>
1127           <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1128          </create>
1129         </osmChange>
1130       CHANGESET
1131
1132       # upload it
1133       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1134       assert_response :bad_request,
1135                       "shouldn't be able to re-use placeholder IDs"
1136     end
1137
1138     def test_upload_process_order
1139       changeset = create(:changeset)
1140
1141       auth_header = basic_authorization_header changeset.user.email, "test"
1142
1143       diff = <<~CHANGESET
1144         <osmChange>
1145         <create>
1146           <node id="-1" lat="1" lon="2" changeset="#{changeset.id}"/>
1147           <way id="-1" changeset="#{changeset.id}">
1148               <nd ref="-1"/>
1149               <nd ref="-2"/>
1150           </way>
1151           <node id="-2" lat="1" lon="2" changeset="#{changeset.id}"/>
1152         </create>
1153         </osmChange>
1154       CHANGESET
1155
1156       # upload it
1157       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1158       assert_response :bad_request,
1159                       "shouldn't refer elements behind it"
1160     end
1161
1162     def test_upload_duplicate_delete
1163       changeset = create(:changeset)
1164
1165       auth_header = basic_authorization_header changeset.user.email, "test"
1166
1167       diff = <<~CHANGESET
1168         <osmChange>
1169           <create>
1170             <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1171           </create>
1172           <delete>
1173             <node id="-1" version="1" changeset="#{changeset.id}" />
1174             <node id="-1" version="1" changeset="#{changeset.id}" />
1175           </delete>
1176         </osmChange>
1177       CHANGESET
1178
1179       # upload it
1180       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1181       assert_response :gone,
1182                       "transaction should be cancelled by second deletion"
1183
1184       diff = <<~CHANGESET
1185         <osmChange>
1186           <create>
1187             <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1188           </create>
1189           <delete if-unused="true">
1190             <node id="-1" version="1" changeset="#{changeset.id}" />
1191             <node id="-1" version="1" changeset="#{changeset.id}" />
1192           </delete>
1193         </osmChange>
1194       CHANGESET
1195
1196       # upload it
1197       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1198
1199       assert_select "diffResult>node", 3
1200       assert_select "diffResult>node[old_id='-1']", 3
1201       assert_select "diffResult>node[new_version='1']", 1
1202       assert_select "diffResult>node[new_version='2']", 1
1203     end
1204
1205     ##
1206     # test that uploading a way referencing invalid placeholders gives a
1207     # proper error, not a 500.
1208     def test_upload_placeholder_invalid_way
1209       changeset = create(:changeset)
1210       way = create(:way)
1211
1212       auth_header = basic_authorization_header changeset.user.email, "test"
1213
1214       diff = <<~CHANGESET
1215         <osmChange>
1216          <create>
1217           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1218           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1219           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1220           <way id="-1" changeset="#{changeset.id}" version="1">
1221            <nd ref="-1"/>
1222            <nd ref="-2"/>
1223            <nd ref="-3"/>
1224            <nd ref="-4"/>
1225           </way>
1226          </create>
1227         </osmChange>
1228       CHANGESET
1229
1230       # upload it
1231       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1232       assert_response :bad_request,
1233                       "shouldn't be able to use invalid placeholder IDs"
1234       assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
1235
1236       # the same again, but this time use an existing way
1237       diff = <<~CHANGESET
1238         <osmChange>
1239          <create>
1240           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1241           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1242           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1243           <way id="#{way.id}" changeset="#{changeset.id}" version="1">
1244            <nd ref="-1"/>
1245            <nd ref="-2"/>
1246            <nd ref="-3"/>
1247            <nd ref="-4"/>
1248           </way>
1249          </create>
1250         </osmChange>
1251       CHANGESET
1252
1253       # upload it
1254       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1255       assert_response :bad_request,
1256                       "shouldn't be able to use invalid placeholder IDs"
1257       assert_equal "Placeholder node not found for reference -4 in way #{way.id}", @response.body
1258     end
1259
1260     ##
1261     # test that uploading a relation referencing invalid placeholders gives a
1262     # proper error, not a 500.
1263     def test_upload_placeholder_invalid_relation
1264       changeset = create(:changeset)
1265       relation = create(:relation)
1266
1267       auth_header = basic_authorization_header changeset.user.email, "test"
1268
1269       diff = <<~CHANGESET
1270         <osmChange>
1271          <create>
1272           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1273           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1274           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1275           <relation id="-1" changeset="#{changeset.id}" version="1">
1276            <member type="node" role="foo" ref="-1"/>
1277            <member type="node" role="foo" ref="-2"/>
1278            <member type="node" role="foo" ref="-3"/>
1279            <member type="node" role="foo" ref="-4"/>
1280           </relation>
1281          </create>
1282         </osmChange>
1283       CHANGESET
1284
1285       # upload it
1286       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1287       assert_response :bad_request,
1288                       "shouldn't be able to use invalid placeholder IDs"
1289       assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
1290
1291       # the same again, but this time use an existing relation
1292       diff = <<~CHANGESET
1293         <osmChange>
1294          <create>
1295           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1296           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1297           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1298           <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
1299            <member type="node" role="foo" ref="-1"/>
1300            <member type="node" role="foo" ref="-2"/>
1301            <member type="node" role="foo" ref="-3"/>
1302            <member type="way" role="bar" ref="-1"/>
1303           </relation>
1304          </create>
1305         </osmChange>
1306       CHANGESET
1307
1308       # upload it
1309       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1310       assert_response :bad_request,
1311                       "shouldn't be able to use invalid placeholder IDs"
1312       assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
1313     end
1314
1315     ##
1316     # test what happens if a diff is uploaded containing only a node
1317     # move.
1318     def test_upload_node_move
1319       auth_header = basic_authorization_header create(:user).email, "test"
1320
1321       xml = "<osm><changeset>" \
1322             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1323             "</changeset></osm>"
1324       put changeset_create_path, :params => xml, :headers => auth_header
1325       assert_response :success
1326       changeset_id = @response.body.to_i
1327
1328       old_node = create(:node, :lat => 1, :lon => 1)
1329
1330       diff = XML::Document.new
1331       diff.root = XML::Node.new "osmChange"
1332       modify = XML::Node.new "modify"
1333       xml_old_node = xml_node_for_node(old_node)
1334       xml_old_node["lat"] = 2.0.to_s
1335       xml_old_node["lon"] = 2.0.to_s
1336       xml_old_node["changeset"] = changeset_id.to_s
1337       modify << xml_old_node
1338       diff.root << modify
1339
1340       # upload it
1341       post changeset_upload_path(:id => changeset_id), :params => diff.to_s, :headers => auth_header
1342       assert_response :success,
1343                       "diff should have uploaded OK"
1344
1345       # check the bbox
1346       changeset = Changeset.find(changeset_id)
1347       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1348       assert_equal 2 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 2 degrees"
1349       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1350       assert_equal 2 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 2 degrees"
1351     end
1352
1353     ##
1354     # test what happens if a diff is uploaded adding a node to a way.
1355     def test_upload_way_extend
1356       auth_header = basic_authorization_header create(:user).email, "test"
1357
1358       xml = "<osm><changeset>" \
1359             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1360             "</changeset></osm>"
1361       put changeset_create_path, :params => xml, :headers => auth_header
1362       assert_response :success
1363       changeset_id = @response.body.to_i
1364
1365       old_way = create(:way)
1366       create(:way_node, :way => old_way, :node => create(:node, :lat => 1, :lon => 1))
1367
1368       diff = XML::Document.new
1369       diff.root = XML::Node.new "osmChange"
1370       modify = XML::Node.new "modify"
1371       xml_old_way = xml_node_for_way(old_way)
1372       nd_ref = XML::Node.new "nd"
1373       nd_ref["ref"] = create(:node, :lat => 3, :lon => 3).id.to_s
1374       xml_old_way << nd_ref
1375       xml_old_way["changeset"] = changeset_id.to_s
1376       modify << xml_old_way
1377       diff.root << modify
1378
1379       # upload it
1380       post changeset_upload_path(:id => changeset_id), :params => diff.to_s, :headers => auth_header
1381       assert_response :success,
1382                       "diff should have uploaded OK"
1383
1384       # check the bbox
1385       changeset = Changeset.find(changeset_id)
1386       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1387       assert_equal 3 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 3 degrees"
1388       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1389       assert_equal 3 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 3 degrees"
1390     end
1391
1392     ##
1393     # test for more issues in #1568
1394     def test_upload_empty_invalid
1395       changeset = create(:changeset)
1396
1397       auth_header = basic_authorization_header changeset.user.email, "test"
1398
1399       ["<osmChange/>",
1400        "<osmChange></osmChange>",
1401        "<osmChange><modify/></osmChange>",
1402        "<osmChange><modify></modify></osmChange>"].each do |diff|
1403         # upload it
1404         post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1405         assert_response(:success, "should be able to upload " \
1406                                   "empty changeset: " + diff)
1407       end
1408     end
1409
1410     ##
1411     # test that the X-Error-Format header works to request XML errors
1412     def test_upload_xml_errors
1413       changeset = create(:changeset)
1414       node = create(:node)
1415       create(:relation_member, :member => node)
1416
1417       auth_header = basic_authorization_header changeset.user.email, "test"
1418
1419       # try and delete a node that is in use
1420       diff = XML::Document.new
1421       diff.root = XML::Node.new "osmChange"
1422       delete = XML::Node.new "delete"
1423       diff.root << delete
1424       delete << xml_node_for_node(node)
1425
1426       # upload it
1427       error_header = error_format_header "xml"
1428       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header.merge(error_header)
1429       assert_response :success,
1430                       "failed to return error in XML format"
1431
1432       # check the returned payload
1433       assert_select "osmError[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
1434       assert_select "osmError>status", 1
1435       assert_select "osmError>message", 1
1436     end
1437
1438     def test_upload_not_found
1439       changeset = create(:changeset)
1440
1441       auth_header = basic_authorization_header changeset.user.email, "test"
1442
1443       # modify node
1444       diff = <<~CHANGESET
1445         <osmChange>
1446         <modify>
1447           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1448         </modify>
1449         </osmChange>
1450       CHANGESET
1451
1452       # upload it
1453       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1454       assert_response :not_found, "Node should not be found"
1455
1456       # modify way
1457       diff = <<~CHANGESET
1458         <osmChange>
1459         <modify>
1460           <way id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1461         </modify>
1462         </osmChange>
1463       CHANGESET
1464
1465       # upload it
1466       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1467       assert_response :not_found, "Way should not be found"
1468
1469       # modify relation
1470       diff = <<~CHANGESET
1471         <osmChange>
1472         <modify>
1473           <relation id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1474         </modify>
1475         </osmChange>
1476       CHANGESET
1477
1478       # upload it
1479       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1480       assert_response :not_found, "Relation should not be found"
1481
1482       # delete node
1483       diff = <<~CHANGESET
1484         <osmChange>
1485         <delete>
1486           <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1487         </delete>
1488         </osmChange>
1489       CHANGESET
1490
1491       # upload it
1492       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1493       assert_response :not_found, "Node should not be deleted"
1494
1495       # delete way
1496       diff = <<~CHANGESET
1497         <osmChange>
1498         <delete>
1499           <way id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1500         </delete>
1501         </osmChange>
1502       CHANGESET
1503
1504       # upload it
1505       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1506       assert_response :not_found, "Way should not be deleted"
1507
1508       # delete relation
1509       diff = <<~CHANGESET
1510         <osmChange>
1511         <delete>
1512           <relation id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
1513         </delete>
1514         </osmChange>
1515       CHANGESET
1516
1517       # upload it
1518       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1519       assert_response :not_found, "Relation should not be deleted"
1520     end
1521
1522     def test_upload_relation_placeholder_not_fix
1523       changeset = create(:changeset)
1524
1525       auth_header = basic_authorization_header changeset.user.email, "test"
1526
1527       # modify node
1528       diff = <<~CHANGESET
1529         <osmChange version='0.6'>
1530           <create>
1531             <relation id='-2' version='0' changeset='#{changeset.id}'>
1532               <member type='relation' role='' ref='-4' />
1533               <tag k='type' v='route' />
1534               <tag k='name' v='AtoB' />
1535             </relation>
1536             <relation id='-3' version='0' changeset='#{changeset.id}'>
1537               <tag k='type' v='route' />
1538               <tag k='name' v='BtoA' />
1539             </relation>
1540             <relation id='-4' version='0' changeset='#{changeset.id}'>
1541               <member type='relation' role='' ref='-2' />
1542               <member type='relation' role='' ref='-3' />
1543               <tag k='type' v='route_master' />
1544               <tag k='name' v='master' />
1545             </relation>
1546           </create>
1547         </osmChange>
1548       CHANGESET
1549
1550       # upload it
1551       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1552       assert_response :bad_request, "shouldn't be able to use reference -4 in relation -2: #{@response.body}"
1553       assert_equal "Placeholder Relation not found for reference -4 in relation -2.", @response.body
1554     end
1555
1556     def test_upload_multiple_delete_block
1557       changeset = create(:changeset)
1558
1559       auth_header = basic_authorization_header changeset.user.email, "test"
1560
1561       node = create(:node)
1562       way = create(:way)
1563       create(:way_node, :way => way, :node => node)
1564       alone_node = create(:node)
1565
1566       # modify node
1567       diff = <<~CHANGESET
1568         <osmChange version='0.6'>
1569           <delete version="0.6">
1570             <node id="#{node.id}" version="#{node.version}" changeset="#{changeset.id}"/>
1571           </delete>
1572           <delete version="0.6" if-unused="true">
1573             <node id="#{alone_node.id}" version="#{alone_node.version}" changeset="#{changeset.id}"/>
1574           </delete>
1575         </osmChange>
1576       CHANGESET
1577
1578       # upload it
1579       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1580       assert_response :precondition_failed,
1581                       "shouldn't be able to upload a invalid deletion diff: #{@response.body}"
1582       assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way.id}.", @response.body
1583     end
1584
1585     ##
1586     # when we make some simple changes we get the same changes back from the
1587     # diff download.
1588     def test_diff_download_simple
1589       node = create(:node)
1590
1591       ## First try with a non-public user, which should get a forbidden
1592       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
1593
1594       # create a temporary changeset
1595       xml = "<osm><changeset>" \
1596             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1597             "</changeset></osm>"
1598       put changeset_create_path, :params => xml, :headers => auth_header
1599       assert_response :forbidden
1600
1601       ## Now try with a normal user
1602       auth_header = basic_authorization_header create(:user).email, "test"
1603
1604       # create a temporary changeset
1605       xml = "<osm><changeset>" \
1606             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1607             "</changeset></osm>"
1608       put changeset_create_path, :params => xml, :headers => auth_header
1609       assert_response :success
1610       changeset_id = @response.body.to_i
1611
1612       # add a diff to it
1613       diff = <<~CHANGESET
1614         <osmChange>
1615          <modify>
1616           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1617           <node id='#{node.id}' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
1618           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
1619           <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
1620           <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
1621           <node id='#{node.id}' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
1622           <node id='#{node.id}' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
1623           <node id='#{node.id}' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
1624          </modify>
1625         </osmChange>
1626       CHANGESET
1627
1628       # upload it
1629       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1630       assert_response :success,
1631                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1632
1633       get changeset_download_path(:id => changeset_id)
1634       assert_response :success
1635
1636       assert_select "osmChange", 1
1637       assert_select "osmChange>modify", 8
1638       assert_select "osmChange>modify>node", 8
1639     end
1640
1641     ##
1642     # culled this from josm to ensure that nothing in the way that josm
1643     # is formatting the request is causing it to fail.
1644     #
1645     # NOTE: the error turned out to be something else completely!
1646     def test_josm_upload
1647       auth_header = basic_authorization_header create(:user).email, "test"
1648
1649       # create a temporary changeset
1650       xml = "<osm><changeset>" \
1651             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1652             "</changeset></osm>"
1653       put changeset_create_path, :params => xml, :headers => auth_header
1654       assert_response :success
1655       changeset_id = @response.body.to_i
1656
1657       diff = <<~OSMFILE
1658         <osmChange version="0.6" generator="JOSM">
1659         <create version="0.6" generator="JOSM">
1660           <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
1661           <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
1662           <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
1663           <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
1664           <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
1665           <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
1666           <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
1667           <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
1668           <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
1669           <way id='-10' action='modify' visible='true' changeset='#{changeset_id}'>
1670             <nd ref='-1' />
1671             <nd ref='-2' />
1672             <nd ref='-3' />
1673             <nd ref='-4' />
1674             <nd ref='-5' />
1675             <nd ref='-6' />
1676             <nd ref='-7' />
1677             <nd ref='-8' />
1678             <nd ref='-9' />
1679             <tag k='highway' v='residential' />
1680             <tag k='name' v='Foobar Street' />
1681           </way>
1682         </create>
1683         </osmChange>
1684       OSMFILE
1685
1686       # upload it
1687       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1688       assert_response :success,
1689                       "can't upload a diff from JOSM: #{@response.body}"
1690
1691       get changeset_download_path(:id => changeset_id)
1692       assert_response :success
1693
1694       assert_select "osmChange", 1
1695       assert_select "osmChange>create>node", 9
1696       assert_select "osmChange>create>way", 1
1697       assert_select "osmChange>create>way>nd", 9
1698       assert_select "osmChange>create>way>tag", 2
1699     end
1700
1701     ##
1702     # when we make some complex changes we get the same changes back from the
1703     # diff download.
1704     def test_diff_download_complex
1705       node = create(:node)
1706       node2 = create(:node)
1707       way = create(:way)
1708       auth_header = basic_authorization_header create(:user).email, "test"
1709
1710       # create a temporary changeset
1711       xml = "<osm><changeset>" \
1712             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1713             "</changeset></osm>"
1714       put changeset_create_path, :params => xml, :headers => auth_header
1715       assert_response :success
1716       changeset_id = @response.body.to_i
1717
1718       # add a diff to it
1719       diff = <<~CHANGESET
1720         <osmChange>
1721          <delete>
1722           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1723          </delete>
1724          <create>
1725           <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
1726           <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
1727           <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
1728          </create>
1729          <modify>
1730           <node id='#{node2.id}' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
1731           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
1732            <nd ref='#{node2.id}'/>
1733            <nd ref='-1'/>
1734            <nd ref='-2'/>
1735            <nd ref='-3'/>
1736           </way>
1737          </modify>
1738         </osmChange>
1739       CHANGESET
1740
1741       # upload it
1742       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1743       assert_response :success,
1744                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1745
1746       get changeset_download_path(:id => changeset_id)
1747       assert_response :success
1748
1749       assert_select "osmChange", 1
1750       assert_select "osmChange>create", 3
1751       assert_select "osmChange>delete", 1
1752       assert_select "osmChange>modify", 2
1753       assert_select "osmChange>create>node", 3
1754       assert_select "osmChange>delete>node", 1
1755       assert_select "osmChange>modify>node", 1
1756       assert_select "osmChange>modify>way", 1
1757     end
1758
1759     def test_changeset_download
1760       changeset = create(:changeset)
1761       node = create(:node, :with_history, :version => 1, :changeset => changeset)
1762       tag = create(:old_node_tag, :old_node => node.old_nodes.find_by(:version => 1))
1763       node2 = create(:node, :with_history, :version => 1, :changeset => changeset)
1764       _node3 = create(:node, :with_history, :deleted, :version => 1, :changeset => changeset)
1765       _relation = create(:relation, :with_history, :version => 1, :changeset => changeset)
1766       _relation2 = create(:relation, :with_history, :deleted, :version => 1, :changeset => changeset)
1767
1768       get changeset_download_path(changeset)
1769
1770       assert_response :success
1771
1772       # FIXME: needs more assert_select tests
1773       assert_select "osmChange[version='#{Settings.api_version}'][generator='#{Settings.generator}']" do
1774         assert_select "create", :count => 5
1775         assert_select "create>node[id='#{node.id}'][visible='#{node.visible?}'][version='#{node.version}']" do
1776           assert_select "tag[k='#{tag.k}'][v='#{tag.v}']"
1777         end
1778         assert_select "create>node[id='#{node2.id}']"
1779       end
1780     end
1781
1782     ##
1783     # check that the bounding box of a changeset gets updated correctly
1784     # FIXME: This should really be moded to a integration test due to the with_controller
1785     def test_changeset_bbox
1786       way = create(:way)
1787       create(:way_node, :way => way, :node => create(:node, :lat => 3, :lon => 3))
1788
1789       auth_header = basic_authorization_header create(:user).email, "test"
1790
1791       # create a new changeset
1792       xml = "<osm><changeset/></osm>"
1793       put changeset_create_path, :params => xml, :headers => auth_header
1794       assert_response :success, "Creating of changeset failed."
1795       changeset_id = @response.body.to_i
1796
1797       # add a single node to it
1798       with_controller(NodesController.new) do
1799         xml = "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
1800         put node_create_path, :params => xml, :headers => auth_header
1801         assert_response :success, "Couldn't create node."
1802       end
1803
1804       # get the bounding box back from the changeset
1805       get changeset_show_path(:id => changeset_id)
1806       assert_response :success, "Couldn't read back changeset."
1807       assert_select "osm>changeset[min_lon='1.0000000']", 1
1808       assert_select "osm>changeset[max_lon='1.0000000']", 1
1809       assert_select "osm>changeset[min_lat='2.0000000']", 1
1810       assert_select "osm>changeset[max_lat='2.0000000']", 1
1811
1812       # add another node to it
1813       with_controller(NodesController.new) do
1814         xml = "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
1815         put node_create_path, :params => xml, :headers => auth_header
1816         assert_response :success, "Couldn't create second node."
1817       end
1818
1819       # get the bounding box back from the changeset
1820       get changeset_show_path(:id => changeset_id)
1821       assert_response :success, "Couldn't read back changeset for the second time."
1822       assert_select "osm>changeset[min_lon='1.0000000']", 1
1823       assert_select "osm>changeset[max_lon='2.0000000']", 1
1824       assert_select "osm>changeset[min_lat='1.0000000']", 1
1825       assert_select "osm>changeset[max_lat='2.0000000']", 1
1826
1827       # add (delete) a way to it, which contains a point at (3,3)
1828       with_controller(WaysController.new) do
1829         xml = update_changeset(xml_for_way(way), changeset_id)
1830         delete api_way_path(way), :params => xml.to_s, :headers => auth_header
1831         assert_response :success, "Couldn't delete a way."
1832       end
1833
1834       # get the bounding box back from the changeset
1835       get changeset_show_path(:id => changeset_id)
1836       assert_response :success, "Couldn't read back changeset for the third time."
1837       assert_select "osm>changeset[min_lon='1.0000000']", 1
1838       assert_select "osm>changeset[max_lon='3.0000000']", 1
1839       assert_select "osm>changeset[min_lat='1.0000000']", 1
1840       assert_select "osm>changeset[max_lat='3.0000000']", 1
1841     end
1842
1843     ##
1844     # test the query functionality of changesets
1845     def test_query
1846       private_user = create(:user, :data_public => false)
1847       private_user_changeset = create(:changeset, :user => private_user)
1848       private_user_closed_changeset = create(:changeset, :closed, :user => private_user)
1849       user = create(:user)
1850       changeset = create(:changeset, :user => user)
1851       closed_changeset = create(:changeset, :closed, :user => user, :created_at => Time.utc(2008, 1, 1, 0, 0, 0), :closed_at => Time.utc(2008, 1, 2, 0, 0, 0))
1852       changeset2 = create(:changeset, :min_lat => (5 * GeoRecord::SCALE).round, :min_lon => (5 * GeoRecord::SCALE).round, :max_lat => (15 * GeoRecord::SCALE).round, :max_lon => (15 * GeoRecord::SCALE).round)
1853       changeset3 = create(:changeset, :min_lat => (4.5 * GeoRecord::SCALE).round, :min_lon => (4.5 * GeoRecord::SCALE).round, :max_lat => (5 * GeoRecord::SCALE).round, :max_lon => (5 * GeoRecord::SCALE).round)
1854
1855       get changesets_path(:bbox => "-10,-10, 10, 10")
1856       assert_response :success, "can't get changesets in bbox"
1857       assert_changesets [changeset2, changeset3]
1858
1859       get changesets_path(:bbox => "4.5,4.5,4.6,4.6")
1860       assert_response :success, "can't get changesets in bbox"
1861       assert_changesets [changeset3]
1862
1863       # not found when looking for changesets of non-existing users
1864       get changesets_path(:user => User.maximum(:id) + 1)
1865       assert_response :not_found
1866       get changesets_path(:display_name => " ")
1867       assert_response :not_found
1868
1869       # can't get changesets of user 1 without authenticating
1870       get changesets_path(:user => private_user.id)
1871       assert_response :not_found, "shouldn't be able to get changesets by non-public user (ID)"
1872       get changesets_path(:display_name => private_user.display_name)
1873       assert_response :not_found, "shouldn't be able to get changesets by non-public user (name)"
1874
1875       # but this should work
1876       auth_header = basic_authorization_header private_user.email, "test"
1877       get changesets_path(:user => private_user.id), :headers => auth_header
1878       assert_response :success, "can't get changesets by user ID"
1879       assert_changesets [private_user_changeset, private_user_closed_changeset]
1880
1881       get changesets_path(:display_name => private_user.display_name), :headers => auth_header
1882       assert_response :success, "can't get changesets by user name"
1883       assert_changesets [private_user_changeset, private_user_closed_changeset]
1884
1885       # test json endpoint
1886       get changesets_path(:display_name => private_user.display_name), :headers => auth_header, :params => { :format => "json" }
1887       assert_response :success, "can't get changesets by user name"
1888
1889       js = ActiveSupport::JSON.decode(@response.body)
1890       assert_not_nil js
1891
1892       assert_equal Settings.api_version, js["version"]
1893       assert_equal "OpenStreetMap server", js["generator"]
1894       assert_equal 2, js["changesets"].count
1895
1896       # check that the correct error is given when we provide both UID and name
1897       get changesets_path(:user => private_user.id,
1898                           :display_name => private_user.display_name), :headers => auth_header
1899       assert_response :bad_request, "should be a bad request to have both ID and name specified"
1900
1901       get changesets_path(:user => private_user.id, :open => true), :headers => auth_header
1902       assert_response :success, "can't get changesets by user and open"
1903       assert_changesets [private_user_changeset]
1904
1905       get changesets_path(:time => "2007-12-31"), :headers => auth_header
1906       assert_response :success, "can't get changesets by time-since"
1907       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1908
1909       get changesets_path(:time => "2008-01-01T12:34Z"), :headers => auth_header
1910       assert_response :success, "can't get changesets by time-since with hour"
1911       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1912
1913       get changesets_path(:time => "2007-12-31T23:59Z,2008-01-02T00:01Z"), :headers => auth_header
1914       assert_response :success, "can't get changesets by time-range"
1915       assert_changesets [closed_changeset]
1916
1917       get changesets_path(:open => "true"), :headers => auth_header
1918       assert_response :success, "can't get changesets by open-ness"
1919       assert_changesets [private_user_changeset, changeset, changeset2, changeset3]
1920
1921       get changesets_path(:closed => "true"), :headers => auth_header
1922       assert_response :success, "can't get changesets by closed-ness"
1923       assert_changesets [private_user_closed_changeset, closed_changeset]
1924
1925       get changesets_path(:closed => "true", :user => private_user.id), :headers => auth_header
1926       assert_response :success, "can't get changesets by closed-ness and user"
1927       assert_changesets [private_user_closed_changeset]
1928
1929       get changesets_path(:closed => "true", :user => user.id), :headers => auth_header
1930       assert_response :success, "can't get changesets by closed-ness and user"
1931       assert_changesets [closed_changeset]
1932
1933       get changesets_path(:changesets => "#{private_user_changeset.id},#{changeset.id},#{closed_changeset.id}"), :headers => auth_header
1934       assert_response :success, "can't get changesets by id (as comma-separated string)"
1935       assert_changesets [private_user_changeset, changeset, closed_changeset]
1936
1937       get changesets_path(:changesets => ""), :headers => auth_header
1938       assert_response :bad_request, "should be a bad request since changesets is empty"
1939     end
1940
1941     ##
1942     # check that errors are returned if garbage is inserted
1943     # into query strings
1944     def test_query_invalid
1945       ["abracadabra!",
1946        "1,2,3,F",
1947        ";drop table users;"].each do |bbox|
1948         get changesets_path(:bbox => bbox)
1949         assert_response :bad_request, "'#{bbox}' isn't a bbox"
1950       end
1951
1952       ["now()",
1953        "00-00-00",
1954        ";drop table users;",
1955        ",",
1956        "-,-"].each do |time|
1957         get changesets_path(:time => time)
1958         assert_response :bad_request, "'#{time}' isn't a valid time range"
1959       end
1960
1961       ["me",
1962        "foobar",
1963        "-1",
1964        "0"].each do |uid|
1965         get changesets_path(:user => uid)
1966         assert_response :bad_request, "'#{uid}' isn't a valid user ID"
1967       end
1968     end
1969
1970     ##
1971     # check updating tags on a changeset
1972     def test_changeset_update
1973       private_user = create(:user, :data_public => false)
1974       private_changeset = create(:changeset, :user => private_user)
1975       user = create(:user)
1976       changeset = create(:changeset, :user => user)
1977
1978       ## First try with a non-public user
1979       new_changeset = create_changeset_xml(:user => private_user)
1980       new_tag = XML::Node.new "tag"
1981       new_tag["k"] = "tagtesting"
1982       new_tag["v"] = "valuetesting"
1983       new_changeset.find("//osm/changeset").first << new_tag
1984
1985       # try without any authorization
1986       put changeset_show_path(private_changeset), :params => new_changeset.to_s
1987       assert_response :unauthorized
1988
1989       # try with the wrong authorization
1990       auth_header = basic_authorization_header create(:user).email, "test"
1991       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
1992       assert_response :conflict
1993
1994       # now this should get an unauthorized
1995       auth_header = basic_authorization_header private_user.email, "test"
1996       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
1997       assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
1998
1999       ## Now try with the public user
2000       new_changeset = create_changeset_xml(:id => 1)
2001       new_tag = XML::Node.new "tag"
2002       new_tag["k"] = "tagtesting"
2003       new_tag["v"] = "valuetesting"
2004       new_changeset.find("//osm/changeset").first << new_tag
2005
2006       # try without any authorization
2007       put changeset_show_path(changeset), :params => new_changeset.to_s
2008       assert_response :unauthorized
2009
2010       # try with the wrong authorization
2011       auth_header = basic_authorization_header create(:user).email, "test"
2012       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2013       assert_response :conflict
2014
2015       # now this should work...
2016       auth_header = basic_authorization_header user.email, "test"
2017       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2018       assert_response :success
2019
2020       assert_select "osm>changeset[id='#{changeset.id}']", 1
2021       assert_select "osm>changeset>tag", 1
2022       assert_select "osm>changeset>tag[k='tagtesting'][v='valuetesting']", 1
2023     end
2024
2025     ##
2026     # check that a user different from the one who opened the changeset
2027     # can't modify it.
2028     def test_changeset_update_invalid
2029       auth_header = basic_authorization_header create(:user).email, "test"
2030
2031       changeset = create(:changeset)
2032       new_changeset = create_changeset_xml(:user => changeset.user, :id => changeset.id)
2033       new_tag = XML::Node.new "tag"
2034       new_tag["k"] = "testing"
2035       new_tag["v"] = "testing"
2036       new_changeset.find("//osm/changeset").first << new_tag
2037
2038       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2039       assert_response :conflict
2040     end
2041
2042     ##
2043     # check that a changeset can contain a certain max number of changes.
2044     ## FIXME should be changed to an integration test due to the with_controller
2045     def test_changeset_limits
2046       auth_header = basic_authorization_header create(:user).email, "test"
2047
2048       # open a new changeset
2049       xml = "<osm><changeset/></osm>"
2050       put changeset_create_path, :params => xml, :headers => auth_header
2051       assert_response :success, "can't create a new changeset"
2052       cs_id = @response.body.to_i
2053
2054       # start the counter just short of where the changeset should finish.
2055       offset = 10
2056       # alter the database to set the counter on the changeset directly,
2057       # otherwise it takes about 6 minutes to fill all of them.
2058       changeset = Changeset.find(cs_id)
2059       changeset.num_changes = Changeset::MAX_ELEMENTS - offset
2060       changeset.save!
2061
2062       with_controller(NodesController.new) do
2063         # create a new node
2064         xml = "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
2065         put node_create_path, :params => xml, :headers => auth_header
2066         assert_response :success, "can't create a new node"
2067         node_id = @response.body.to_i
2068
2069         get api_node_path(:id => node_id)
2070         assert_response :success, "can't read back new node"
2071         node_doc = XML::Parser.string(@response.body).parse
2072         node_xml = node_doc.find("//osm/node").first
2073
2074         # loop until we fill the changeset with nodes
2075         offset.times do |i|
2076           node_xml["lat"] = rand.to_s
2077           node_xml["lon"] = rand.to_s
2078           node_xml["version"] = (i + 1).to_s
2079
2080           put api_node_path(:id => node_id), :params => node_doc.to_s, :headers => auth_header
2081           assert_response :success, "attempt #{i} should have succeeded"
2082         end
2083
2084         # trying again should fail
2085         node_xml["lat"] = rand.to_s
2086         node_xml["lon"] = rand.to_s
2087         node_xml["version"] = offset.to_s
2088
2089         put api_node_path(:id => node_id), :params => node_doc.to_s, :headers => auth_header
2090         assert_response :conflict, "final attempt should have failed"
2091       end
2092
2093       changeset = Changeset.find(cs_id)
2094       assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
2095
2096       # check that the changeset is now closed as well
2097       assert_not(changeset.open?,
2098                  "changeset should have been auto-closed by exceeding " \
2099                  "element limit.")
2100     end
2101
2102     ##
2103     # check that the changeset download for a changeset with a redacted
2104     # element in it doesn't contain that element.
2105     def test_diff_download_redacted
2106       changeset = create(:changeset)
2107       node = create(:node, :with_history, :version => 2, :changeset => changeset)
2108       node_v1 = node.old_nodes.find_by(:version => 1)
2109       node_v1.redact!(create(:redaction))
2110
2111       get changeset_download_path(changeset)
2112       assert_response :success
2113
2114       assert_select "osmChange", 1
2115       # this changeset contains the node in versions 1 & 2, but 1 should
2116       # be hidden.
2117       assert_select "osmChange node[id='#{node.id}']", 1
2118       assert_select "osmChange node[id='#{node.id}'][version='1']", 0
2119     end
2120
2121     ##
2122     # test subscribe success
2123     def test_subscribe_success
2124       auth_header = basic_authorization_header create(:user).email, "test"
2125       changeset = create(:changeset, :closed)
2126
2127       assert_difference "changeset.subscribers.count", 1 do
2128         post changeset_subscribe_path(changeset), :headers => auth_header
2129       end
2130       assert_response :success
2131
2132       # not closed changeset
2133       changeset = create(:changeset)
2134       assert_difference "changeset.subscribers.count", 1 do
2135         post changeset_subscribe_path(changeset), :headers => auth_header
2136       end
2137       assert_response :success
2138     end
2139
2140     ##
2141     # test subscribe fail
2142     def test_subscribe_fail
2143       user = create(:user)
2144
2145       # unauthorized
2146       changeset = create(:changeset, :closed)
2147       assert_no_difference "changeset.subscribers.count" do
2148         post changeset_subscribe_path(changeset)
2149       end
2150       assert_response :unauthorized
2151
2152       auth_header = basic_authorization_header user.email, "test"
2153
2154       # bad changeset id
2155       assert_no_difference "changeset.subscribers.count" do
2156         post changeset_subscribe_path(:id => 999111), :headers => auth_header
2157       end
2158       assert_response :not_found
2159
2160       # trying to subscribe when already subscribed
2161       changeset = create(:changeset, :closed)
2162       changeset.subscribers.push(user)
2163       assert_no_difference "changeset.subscribers.count" do
2164         post changeset_subscribe_path(changeset), :headers => auth_header
2165       end
2166       assert_response :conflict
2167     end
2168
2169     ##
2170     # test unsubscribe success
2171     def test_unsubscribe_success
2172       user = create(:user)
2173       auth_header = basic_authorization_header user.email, "test"
2174       changeset = create(:changeset, :closed)
2175       changeset.subscribers.push(user)
2176
2177       assert_difference "changeset.subscribers.count", -1 do
2178         post changeset_unsubscribe_path(changeset), :headers => auth_header
2179       end
2180       assert_response :success
2181
2182       # not closed changeset
2183       changeset = create(:changeset)
2184       changeset.subscribers.push(user)
2185
2186       assert_difference "changeset.subscribers.count", -1 do
2187         post changeset_unsubscribe_path(changeset), :headers => auth_header
2188       end
2189       assert_response :success
2190     end
2191
2192     ##
2193     # test unsubscribe fail
2194     def test_unsubscribe_fail
2195       # unauthorized
2196       changeset = create(:changeset, :closed)
2197       assert_no_difference "changeset.subscribers.count" do
2198         post changeset_unsubscribe_path(changeset)
2199       end
2200       assert_response :unauthorized
2201
2202       auth_header = basic_authorization_header create(:user).email, "test"
2203
2204       # bad changeset id
2205       assert_no_difference "changeset.subscribers.count" do
2206         post changeset_unsubscribe_path(:id => 999111), :headers => auth_header
2207       end
2208       assert_response :not_found
2209
2210       # trying to unsubscribe when not subscribed
2211       changeset = create(:changeset, :closed)
2212       assert_no_difference "changeset.subscribers.count" do
2213         post changeset_unsubscribe_path(changeset), :headers => auth_header
2214       end
2215       assert_response :not_found
2216     end
2217
2218     private
2219
2220     ##
2221     # boilerplate for checking that certain changesets exist in the
2222     # output.
2223     def assert_changesets(changesets)
2224       assert_select "osm>changeset", changesets.size
2225       changesets.each do |changeset|
2226         assert_select "osm>changeset[id='#{changeset.id}']", 1
2227       end
2228     end
2229
2230     ##
2231     # update the changeset_id of a way element
2232     def update_changeset(xml, changeset_id)
2233       xml_attr_rewrite(xml, "changeset", changeset_id)
2234     end
2235
2236     ##
2237     # update an attribute in a way element
2238     def xml_attr_rewrite(xml, name, value)
2239       xml.find("//osm/way").first[name] = value.to_s
2240       xml
2241     end
2242
2243     ##
2244     # build XML for changesets
2245     def create_changeset_xml(user: nil, id: nil)
2246       root = XML::Document.new
2247       root.root = XML::Node.new "osm"
2248       cs = XML::Node.new "changeset"
2249       if user
2250         cs["user"] = user.display_name
2251         cs["uid"] = user.id.to_s
2252       end
2253       cs["id"] = id.to_s if id
2254       root.root << cs
2255       root
2256     end
2257   end
2258 end