]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets_controller_test.rb
Merge pull request #3495 from mmd-osm/patch/json_cs
[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     end
1120
1121     ##
1122     # test that uploading a way referencing invalid placeholders gives a
1123     # proper error, not a 500.
1124     def test_upload_placeholder_invalid_way
1125       changeset = create(:changeset)
1126       way = create(:way)
1127
1128       auth_header = basic_authorization_header changeset.user.email, "test"
1129
1130       diff = <<~CHANGESET
1131         <osmChange>
1132          <create>
1133           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1134           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1135           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1136           <way id="-1" changeset="#{changeset.id}" version="1">
1137            <nd ref="-1"/>
1138            <nd ref="-2"/>
1139            <nd ref="-3"/>
1140            <nd ref="-4"/>
1141           </way>
1142          </create>
1143         </osmChange>
1144       CHANGESET
1145
1146       # upload it
1147       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1148       assert_response :bad_request,
1149                       "shouldn't be able to use invalid placeholder IDs"
1150       assert_equal "Placeholder node not found for reference -4 in way -1", @response.body
1151
1152       # the same again, but this time use an existing way
1153       diff = <<~CHANGESET
1154         <osmChange>
1155          <create>
1156           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1157           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1158           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1159           <way id="#{way.id}" changeset="#{changeset.id}" version="1">
1160            <nd ref="-1"/>
1161            <nd ref="-2"/>
1162            <nd ref="-3"/>
1163            <nd ref="-4"/>
1164           </way>
1165          </create>
1166         </osmChange>
1167       CHANGESET
1168
1169       # upload it
1170       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1171       assert_response :bad_request,
1172                       "shouldn't be able to use invalid placeholder IDs"
1173       assert_equal "Placeholder node not found for reference -4 in way #{way.id}", @response.body
1174     end
1175
1176     ##
1177     # test that uploading a relation referencing invalid placeholders gives a
1178     # proper error, not a 500.
1179     def test_upload_placeholder_invalid_relation
1180       changeset = create(:changeset)
1181       relation = create(:relation)
1182
1183       auth_header = basic_authorization_header changeset.user.email, "test"
1184
1185       diff = <<~CHANGESET
1186         <osmChange>
1187          <create>
1188           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1189           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1190           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1191           <relation id="-1" changeset="#{changeset.id}" version="1">
1192            <member type="node" role="foo" ref="-1"/>
1193            <member type="node" role="foo" ref="-2"/>
1194            <member type="node" role="foo" ref="-3"/>
1195            <member type="node" role="foo" ref="-4"/>
1196           </relation>
1197          </create>
1198         </osmChange>
1199       CHANGESET
1200
1201       # upload it
1202       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1203       assert_response :bad_request,
1204                       "shouldn't be able to use invalid placeholder IDs"
1205       assert_equal "Placeholder Node not found for reference -4 in relation -1.", @response.body
1206
1207       # the same again, but this time use an existing relation
1208       diff = <<~CHANGESET
1209         <osmChange>
1210          <create>
1211           <node id="-1" lon="0" lat="0" changeset="#{changeset.id}" version="1"/>
1212           <node id="-2" lon="1" lat="1" changeset="#{changeset.id}" version="1"/>
1213           <node id="-3" lon="2" lat="2" changeset="#{changeset.id}" version="1"/>
1214           <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
1215            <member type="node" role="foo" ref="-1"/>
1216            <member type="node" role="foo" ref="-2"/>
1217            <member type="node" role="foo" ref="-3"/>
1218            <member type="way" role="bar" ref="-1"/>
1219           </relation>
1220          </create>
1221         </osmChange>
1222       CHANGESET
1223
1224       # upload it
1225       post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1226       assert_response :bad_request,
1227                       "shouldn't be able to use invalid placeholder IDs"
1228       assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
1229     end
1230
1231     ##
1232     # test what happens if a diff is uploaded containing only a node
1233     # move.
1234     def test_upload_node_move
1235       auth_header = basic_authorization_header create(:user).email, "test"
1236
1237       xml = "<osm><changeset>" \
1238             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1239             "</changeset></osm>"
1240       put changeset_create_path, :params => xml, :headers => auth_header
1241       assert_response :success
1242       changeset_id = @response.body.to_i
1243
1244       old_node = create(:node, :lat => 1, :lon => 1)
1245
1246       diff = XML::Document.new
1247       diff.root = XML::Node.new "osmChange"
1248       modify = XML::Node.new "modify"
1249       xml_old_node = xml_node_for_node(old_node)
1250       xml_old_node["lat"] = 2.0.to_s
1251       xml_old_node["lon"] = 2.0.to_s
1252       xml_old_node["changeset"] = changeset_id.to_s
1253       modify << xml_old_node
1254       diff.root << modify
1255
1256       # upload it
1257       post changeset_upload_path(:id => changeset_id), :params => diff.to_s, :headers => auth_header
1258       assert_response :success,
1259                       "diff should have uploaded OK"
1260
1261       # check the bbox
1262       changeset = Changeset.find(changeset_id)
1263       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1264       assert_equal 2 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 2 degrees"
1265       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1266       assert_equal 2 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 2 degrees"
1267     end
1268
1269     ##
1270     # test what happens if a diff is uploaded adding a node to a way.
1271     def test_upload_way_extend
1272       auth_header = basic_authorization_header create(:user).email, "test"
1273
1274       xml = "<osm><changeset>" \
1275             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1276             "</changeset></osm>"
1277       put changeset_create_path, :params => xml, :headers => auth_header
1278       assert_response :success
1279       changeset_id = @response.body.to_i
1280
1281       old_way = create(:way)
1282       create(:way_node, :way => old_way, :node => create(:node, :lat => 1, :lon => 1))
1283
1284       diff = XML::Document.new
1285       diff.root = XML::Node.new "osmChange"
1286       modify = XML::Node.new "modify"
1287       xml_old_way = xml_node_for_way(old_way)
1288       nd_ref = XML::Node.new "nd"
1289       nd_ref["ref"] = create(:node, :lat => 3, :lon => 3).id.to_s
1290       xml_old_way << nd_ref
1291       xml_old_way["changeset"] = changeset_id.to_s
1292       modify << xml_old_way
1293       diff.root << modify
1294
1295       # upload it
1296       post changeset_upload_path(:id => changeset_id), :params => diff.to_s, :headers => auth_header
1297       assert_response :success,
1298                       "diff should have uploaded OK"
1299
1300       # check the bbox
1301       changeset = Changeset.find(changeset_id)
1302       assert_equal 1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 1 degree"
1303       assert_equal 3 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 3 degrees"
1304       assert_equal 1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1 degree"
1305       assert_equal 3 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 3 degrees"
1306     end
1307
1308     ##
1309     # test for more issues in #1568
1310     def test_upload_empty_invalid
1311       changeset = create(:changeset)
1312
1313       auth_header = basic_authorization_header changeset.user.email, "test"
1314
1315       ["<osmChange/>",
1316        "<osmChange></osmChange>",
1317        "<osmChange><modify/></osmChange>",
1318        "<osmChange><modify></modify></osmChange>"].each do |diff|
1319         # upload it
1320         post changeset_upload_path(changeset), :params => diff, :headers => auth_header
1321         assert_response(:success, "should be able to upload " \
1322                                   "empty changeset: " + diff)
1323       end
1324     end
1325
1326     ##
1327     # test that the X-Error-Format header works to request XML errors
1328     def test_upload_xml_errors
1329       changeset = create(:changeset)
1330       node = create(:node)
1331       create(:relation_member, :member => node)
1332
1333       auth_header = basic_authorization_header changeset.user.email, "test"
1334
1335       # try and delete a node that is in use
1336       diff = XML::Document.new
1337       diff.root = XML::Node.new "osmChange"
1338       delete = XML::Node.new "delete"
1339       diff.root << delete
1340       delete << xml_node_for_node(node)
1341
1342       # upload it
1343       error_header = error_format_header "xml"
1344       post changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header.merge(error_header)
1345       assert_response :success,
1346                       "failed to return error in XML format"
1347
1348       # check the returned payload
1349       assert_select "osmError[version='#{Settings.api_version}'][generator='OpenStreetMap server']", 1
1350       assert_select "osmError>status", 1
1351       assert_select "osmError>message", 1
1352     end
1353
1354     ##
1355     # when we make some simple changes we get the same changes back from the
1356     # diff download.
1357     def test_diff_download_simple
1358       node = create(:node)
1359
1360       ## First try with a non-public user, which should get a forbidden
1361       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
1362
1363       # create a temporary changeset
1364       xml = "<osm><changeset>" \
1365             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1366             "</changeset></osm>"
1367       put changeset_create_path, :params => xml, :headers => auth_header
1368       assert_response :forbidden
1369
1370       ## Now try with a normal user
1371       auth_header = basic_authorization_header create(:user).email, "test"
1372
1373       # create a temporary changeset
1374       xml = "<osm><changeset>" \
1375             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1376             "</changeset></osm>"
1377       put changeset_create_path, :params => xml, :headers => auth_header
1378       assert_response :success
1379       changeset_id = @response.body.to_i
1380
1381       # add a diff to it
1382       diff = <<~CHANGESET
1383         <osmChange>
1384          <modify>
1385           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1386           <node id='#{node.id}' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
1387           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
1388           <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
1389           <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
1390           <node id='#{node.id}' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
1391           <node id='#{node.id}' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
1392           <node id='#{node.id}' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
1393          </modify>
1394         </osmChange>
1395       CHANGESET
1396
1397       # upload it
1398       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1399       assert_response :success,
1400                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1401
1402       get changeset_download_path(:id => changeset_id)
1403       assert_response :success
1404
1405       assert_select "osmChange", 1
1406       assert_select "osmChange>modify", 8
1407       assert_select "osmChange>modify>node", 8
1408     end
1409
1410     ##
1411     # culled this from josm to ensure that nothing in the way that josm
1412     # is formatting the request is causing it to fail.
1413     #
1414     # NOTE: the error turned out to be something else completely!
1415     def test_josm_upload
1416       auth_header = basic_authorization_header create(:user).email, "test"
1417
1418       # create a temporary changeset
1419       xml = "<osm><changeset>" \
1420             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1421             "</changeset></osm>"
1422       put changeset_create_path, :params => xml, :headers => auth_header
1423       assert_response :success
1424       changeset_id = @response.body.to_i
1425
1426       diff = <<~OSMFILE
1427         <osmChange version="0.6" generator="JOSM">
1428         <create version="0.6" generator="JOSM">
1429           <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
1430           <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
1431           <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
1432           <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
1433           <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
1434           <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
1435           <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
1436           <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
1437           <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
1438           <way id='-10' action='modify' visible='true' changeset='#{changeset_id}'>
1439             <nd ref='-1' />
1440             <nd ref='-2' />
1441             <nd ref='-3' />
1442             <nd ref='-4' />
1443             <nd ref='-5' />
1444             <nd ref='-6' />
1445             <nd ref='-7' />
1446             <nd ref='-8' />
1447             <nd ref='-9' />
1448             <tag k='highway' v='residential' />
1449             <tag k='name' v='Foobar Street' />
1450           </way>
1451         </create>
1452         </osmChange>
1453       OSMFILE
1454
1455       # upload it
1456       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1457       assert_response :success,
1458                       "can't upload a diff from JOSM: #{@response.body}"
1459
1460       get changeset_download_path(:id => changeset_id)
1461       assert_response :success
1462
1463       assert_select "osmChange", 1
1464       assert_select "osmChange>create>node", 9
1465       assert_select "osmChange>create>way", 1
1466       assert_select "osmChange>create>way>nd", 9
1467       assert_select "osmChange>create>way>tag", 2
1468     end
1469
1470     ##
1471     # when we make some complex changes we get the same changes back from the
1472     # diff download.
1473     def test_diff_download_complex
1474       node = create(:node)
1475       node2 = create(:node)
1476       way = create(:way)
1477       auth_header = basic_authorization_header create(:user).email, "test"
1478
1479       # create a temporary changeset
1480       xml = "<osm><changeset>" \
1481             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1482             "</changeset></osm>"
1483       put changeset_create_path, :params => xml, :headers => auth_header
1484       assert_response :success
1485       changeset_id = @response.body.to_i
1486
1487       # add a diff to it
1488       diff = <<~CHANGESET
1489         <osmChange>
1490          <delete>
1491           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1492          </delete>
1493          <create>
1494           <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
1495           <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
1496           <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
1497          </create>
1498          <modify>
1499           <node id='#{node2.id}' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
1500           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
1501            <nd ref='#{node2.id}'/>
1502            <nd ref='-1'/>
1503            <nd ref='-2'/>
1504            <nd ref='-3'/>
1505           </way>
1506          </modify>
1507         </osmChange>
1508       CHANGESET
1509
1510       # upload it
1511       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1512       assert_response :success,
1513                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1514
1515       get changeset_download_path(:id => changeset_id)
1516       assert_response :success
1517
1518       assert_select "osmChange", 1
1519       assert_select "osmChange>create", 3
1520       assert_select "osmChange>delete", 1
1521       assert_select "osmChange>modify", 2
1522       assert_select "osmChange>create>node", 3
1523       assert_select "osmChange>delete>node", 1
1524       assert_select "osmChange>modify>node", 1
1525       assert_select "osmChange>modify>way", 1
1526     end
1527
1528     def test_changeset_download
1529       changeset = create(:changeset)
1530       node = create(:node, :with_history, :version => 1, :changeset => changeset)
1531       tag = create(:old_node_tag, :old_node => node.old_nodes.find_by(:version => 1))
1532       node2 = create(:node, :with_history, :version => 1, :changeset => changeset)
1533       _node3 = create(:node, :with_history, :deleted, :version => 1, :changeset => changeset)
1534       _relation = create(:relation, :with_history, :version => 1, :changeset => changeset)
1535       _relation2 = create(:relation, :with_history, :deleted, :version => 1, :changeset => changeset)
1536
1537       get changeset_download_path(changeset)
1538
1539       assert_response :success
1540       # print @response.body
1541       # FIXME: needs more assert_select tests
1542       assert_select "osmChange[version='#{Settings.api_version}'][generator='#{Settings.generator}']" do
1543         assert_select "create", :count => 5
1544         assert_select "create>node[id='#{node.id}'][visible='#{node.visible?}'][version='#{node.version}']" do
1545           assert_select "tag[k='#{tag.k}'][v='#{tag.v}']"
1546         end
1547         assert_select "create>node[id='#{node2.id}']"
1548       end
1549     end
1550
1551     ##
1552     # check that the bounding box of a changeset gets updated correctly
1553     # FIXME: This should really be moded to a integration test due to the with_controller
1554     def test_changeset_bbox
1555       way = create(:way)
1556       create(:way_node, :way => way, :node => create(:node, :lat => 3, :lon => 3))
1557
1558       auth_header = basic_authorization_header create(:user).email, "test"
1559
1560       # create a new changeset
1561       xml = "<osm><changeset/></osm>"
1562       put changeset_create_path, :params => xml, :headers => auth_header
1563       assert_response :success, "Creating of changeset failed."
1564       changeset_id = @response.body.to_i
1565
1566       # add a single node to it
1567       with_controller(NodesController.new) do
1568         xml = "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
1569         put node_create_path, :params => xml, :headers => auth_header
1570         assert_response :success, "Couldn't create node."
1571       end
1572
1573       # get the bounding box back from the changeset
1574       get changeset_show_path(:id => changeset_id)
1575       assert_response :success, "Couldn't read back changeset."
1576       assert_select "osm>changeset[min_lon='1.0000000']", 1
1577       assert_select "osm>changeset[max_lon='1.0000000']", 1
1578       assert_select "osm>changeset[min_lat='2.0000000']", 1
1579       assert_select "osm>changeset[max_lat='2.0000000']", 1
1580
1581       # add another node to it
1582       with_controller(NodesController.new) do
1583         xml = "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
1584         put node_create_path, :params => xml, :headers => auth_header
1585         assert_response :success, "Couldn't create second node."
1586       end
1587
1588       # get the bounding box back from the changeset
1589       get changeset_show_path(:id => changeset_id)
1590       assert_response :success, "Couldn't read back changeset for the second time."
1591       assert_select "osm>changeset[min_lon='1.0000000']", 1
1592       assert_select "osm>changeset[max_lon='2.0000000']", 1
1593       assert_select "osm>changeset[min_lat='1.0000000']", 1
1594       assert_select "osm>changeset[max_lat='2.0000000']", 1
1595
1596       # add (delete) a way to it, which contains a point at (3,3)
1597       with_controller(WaysController.new) do
1598         xml = update_changeset(xml_for_way(way), changeset_id)
1599         delete api_way_path(way), :params => xml.to_s, :headers => auth_header
1600         assert_response :success, "Couldn't delete a way."
1601       end
1602
1603       # get the bounding box back from the changeset
1604       get changeset_show_path(:id => changeset_id)
1605       assert_response :success, "Couldn't read back changeset for the third time."
1606       assert_select "osm>changeset[min_lon='1.0000000']", 1
1607       assert_select "osm>changeset[max_lon='3.0000000']", 1
1608       assert_select "osm>changeset[min_lat='1.0000000']", 1
1609       assert_select "osm>changeset[max_lat='3.0000000']", 1
1610     end
1611
1612     ##
1613     # test the query functionality of changesets
1614     def test_query
1615       private_user = create(:user, :data_public => false)
1616       private_user_changeset = create(:changeset, :user => private_user)
1617       private_user_closed_changeset = create(:changeset, :closed, :user => private_user)
1618       user = create(:user)
1619       changeset = create(:changeset, :user => user)
1620       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))
1621       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)
1622       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)
1623
1624       get changesets_path(:bbox => "-10,-10, 10, 10")
1625       assert_response :success, "can't get changesets in bbox"
1626       assert_changesets [changeset2, changeset3]
1627
1628       get changesets_path(:bbox => "4.5,4.5,4.6,4.6")
1629       assert_response :success, "can't get changesets in bbox"
1630       assert_changesets [changeset3]
1631
1632       # not found when looking for changesets of non-existing users
1633       get changesets_path(:user => User.maximum(:id) + 1)
1634       assert_response :not_found
1635       get changesets_path(:display_name => " ")
1636       assert_response :not_found
1637
1638       # can't get changesets of user 1 without authenticating
1639       get changesets_path(:user => private_user.id)
1640       assert_response :not_found, "shouldn't be able to get changesets by non-public user (ID)"
1641       get changesets_path(:display_name => private_user.display_name)
1642       assert_response :not_found, "shouldn't be able to get changesets by non-public user (name)"
1643
1644       # but this should work
1645       auth_header = basic_authorization_header private_user.email, "test"
1646       get changesets_path(:user => private_user.id), :headers => auth_header
1647       assert_response :success, "can't get changesets by user ID"
1648       assert_changesets [private_user_changeset, private_user_closed_changeset]
1649
1650       get changesets_path(:display_name => private_user.display_name), :headers => auth_header
1651       assert_response :success, "can't get changesets by user name"
1652       assert_changesets [private_user_changeset, private_user_closed_changeset]
1653
1654       # test json endpoint
1655       get changesets_path(:display_name => private_user.display_name), :headers => auth_header, :params => { :format => "json" }
1656       assert_response :success, "can't get changesets by user name"
1657
1658       js = ActiveSupport::JSON.decode(@response.body)
1659       assert_not_nil js
1660
1661       assert_equal Settings.api_version, js["version"]
1662       assert_equal "OpenStreetMap server", js["generator"]
1663       assert_equal 2, js["changesets"].count
1664
1665       # check that the correct error is given when we provide both UID and name
1666       get changesets_path(:user => private_user.id,
1667                           :display_name => private_user.display_name), :headers => auth_header
1668       assert_response :bad_request, "should be a bad request to have both ID and name specified"
1669
1670       get changesets_path(:user => private_user.id, :open => true), :headers => auth_header
1671       assert_response :success, "can't get changesets by user and open"
1672       assert_changesets [private_user_changeset]
1673
1674       get changesets_path(:time => "2007-12-31"), :headers => auth_header
1675       assert_response :success, "can't get changesets by time-since"
1676       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1677
1678       get changesets_path(:time => "2008-01-01T12:34Z"), :headers => auth_header
1679       assert_response :success, "can't get changesets by time-since with hour"
1680       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1681
1682       get changesets_path(:time => "2007-12-31T23:59Z,2008-01-02T00:01Z"), :headers => auth_header
1683       assert_response :success, "can't get changesets by time-range"
1684       assert_changesets [closed_changeset]
1685
1686       get changesets_path(:open => "true"), :headers => auth_header
1687       assert_response :success, "can't get changesets by open-ness"
1688       assert_changesets [private_user_changeset, changeset, changeset2, changeset3]
1689
1690       get changesets_path(:closed => "true"), :headers => auth_header
1691       assert_response :success, "can't get changesets by closed-ness"
1692       assert_changesets [private_user_closed_changeset, closed_changeset]
1693
1694       get changesets_path(:closed => "true", :user => private_user.id), :headers => auth_header
1695       assert_response :success, "can't get changesets by closed-ness and user"
1696       assert_changesets [private_user_closed_changeset]
1697
1698       get changesets_path(:closed => "true", :user => user.id), :headers => auth_header
1699       assert_response :success, "can't get changesets by closed-ness and user"
1700       assert_changesets [closed_changeset]
1701
1702       get changesets_path(:changesets => "#{private_user_changeset.id},#{changeset.id},#{closed_changeset.id}"), :headers => auth_header
1703       assert_response :success, "can't get changesets by id (as comma-separated string)"
1704       assert_changesets [private_user_changeset, changeset, closed_changeset]
1705
1706       get changesets_path(:changesets => ""), :headers => auth_header
1707       assert_response :bad_request, "should be a bad request since changesets is empty"
1708     end
1709
1710     ##
1711     # check that errors are returned if garbage is inserted
1712     # into query strings
1713     def test_query_invalid
1714       ["abracadabra!",
1715        "1,2,3,F",
1716        ";drop table users;"].each do |bbox|
1717         get changesets_path(:bbox => bbox)
1718         assert_response :bad_request, "'#{bbox}' isn't a bbox"
1719       end
1720
1721       ["now()",
1722        "00-00-00",
1723        ";drop table users;",
1724        ",",
1725        "-,-"].each do |time|
1726         get changesets_path(:time => time)
1727         assert_response :bad_request, "'#{time}' isn't a valid time range"
1728       end
1729
1730       ["me",
1731        "foobar",
1732        "-1",
1733        "0"].each do |uid|
1734         get changesets_path(:user => uid)
1735         assert_response :bad_request, "'#{uid}' isn't a valid user ID"
1736       end
1737     end
1738
1739     ##
1740     # check updating tags on a changeset
1741     def test_changeset_update
1742       private_user = create(:user, :data_public => false)
1743       private_changeset = create(:changeset, :user => private_user)
1744       user = create(:user)
1745       changeset = create(:changeset, :user => user)
1746
1747       ## First try with a non-public user
1748       new_changeset = create_changeset_xml(:user => private_user)
1749       new_tag = XML::Node.new "tag"
1750       new_tag["k"] = "tagtesting"
1751       new_tag["v"] = "valuetesting"
1752       new_changeset.find("//osm/changeset").first << new_tag
1753
1754       # try without any authorization
1755       put changeset_show_path(private_changeset), :params => new_changeset.to_s
1756       assert_response :unauthorized
1757
1758       # try with the wrong authorization
1759       auth_header = basic_authorization_header create(:user).email, "test"
1760       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
1761       assert_response :conflict
1762
1763       # now this should get an unauthorized
1764       auth_header = basic_authorization_header private_user.email, "test"
1765       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
1766       assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
1767
1768       ## Now try with the public user
1769       new_changeset = create_changeset_xml(:id => 1)
1770       new_tag = XML::Node.new "tag"
1771       new_tag["k"] = "tagtesting"
1772       new_tag["v"] = "valuetesting"
1773       new_changeset.find("//osm/changeset").first << new_tag
1774
1775       # try without any authorization
1776       put changeset_show_path(changeset), :params => new_changeset.to_s
1777       assert_response :unauthorized
1778
1779       # try with the wrong authorization
1780       auth_header = basic_authorization_header create(:user).email, "test"
1781       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
1782       assert_response :conflict
1783
1784       # now this should work...
1785       auth_header = basic_authorization_header user.email, "test"
1786       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
1787       assert_response :success
1788
1789       assert_select "osm>changeset[id='#{changeset.id}']", 1
1790       assert_select "osm>changeset>tag", 1
1791       assert_select "osm>changeset>tag[k='tagtesting'][v='valuetesting']", 1
1792     end
1793
1794     ##
1795     # check that a user different from the one who opened the changeset
1796     # can't modify it.
1797     def test_changeset_update_invalid
1798       auth_header = basic_authorization_header create(:user).email, "test"
1799
1800       changeset = create(:changeset)
1801       new_changeset = create_changeset_xml(:user => changeset.user, :id => changeset.id)
1802       new_tag = XML::Node.new "tag"
1803       new_tag["k"] = "testing"
1804       new_tag["v"] = "testing"
1805       new_changeset.find("//osm/changeset").first << new_tag
1806
1807       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
1808       assert_response :conflict
1809     end
1810
1811     ##
1812     # check that a changeset can contain a certain max number of changes.
1813     ## FIXME should be changed to an integration test due to the with_controller
1814     def test_changeset_limits
1815       auth_header = basic_authorization_header create(:user).email, "test"
1816
1817       # open a new changeset
1818       xml = "<osm><changeset/></osm>"
1819       put changeset_create_path, :params => xml, :headers => auth_header
1820       assert_response :success, "can't create a new changeset"
1821       cs_id = @response.body.to_i
1822
1823       # start the counter just short of where the changeset should finish.
1824       offset = 10
1825       # alter the database to set the counter on the changeset directly,
1826       # otherwise it takes about 6 minutes to fill all of them.
1827       changeset = Changeset.find(cs_id)
1828       changeset.num_changes = Changeset::MAX_ELEMENTS - offset
1829       changeset.save!
1830
1831       with_controller(NodesController.new) do
1832         # create a new node
1833         xml = "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
1834         put node_create_path, :params => xml, :headers => auth_header
1835         assert_response :success, "can't create a new node"
1836         node_id = @response.body.to_i
1837
1838         get api_node_path(:id => node_id)
1839         assert_response :success, "can't read back new node"
1840         node_doc = XML::Parser.string(@response.body).parse
1841         node_xml = node_doc.find("//osm/node").first
1842
1843         # loop until we fill the changeset with nodes
1844         offset.times do |i|
1845           node_xml["lat"] = rand.to_s
1846           node_xml["lon"] = rand.to_s
1847           node_xml["version"] = (i + 1).to_s
1848
1849           put api_node_path(:id => node_id), :params => node_doc.to_s, :headers => auth_header
1850           assert_response :success, "attempt #{i} should have succeeded"
1851         end
1852
1853         # trying again should fail
1854         node_xml["lat"] = rand.to_s
1855         node_xml["lon"] = rand.to_s
1856         node_xml["version"] = offset.to_s
1857
1858         put api_node_path(:id => node_id), :params => node_doc.to_s, :headers => auth_header
1859         assert_response :conflict, "final attempt should have failed"
1860       end
1861
1862       changeset = Changeset.find(cs_id)
1863       assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
1864
1865       # check that the changeset is now closed as well
1866       assert_not(changeset.open?,
1867                  "changeset should have been auto-closed by exceeding " \
1868                  "element limit.")
1869     end
1870
1871     ##
1872     # check that the changeset download for a changeset with a redacted
1873     # element in it doesn't contain that element.
1874     def test_diff_download_redacted
1875       changeset = create(:changeset)
1876       node = create(:node, :with_history, :version => 2, :changeset => changeset)
1877       node_v1 = node.old_nodes.find_by(:version => 1)
1878       node_v1.redact!(create(:redaction))
1879
1880       get changeset_download_path(changeset)
1881       assert_response :success
1882
1883       assert_select "osmChange", 1
1884       # this changeset contains the node in versions 1 & 2, but 1 should
1885       # be hidden.
1886       assert_select "osmChange node[id='#{node.id}']", 1
1887       assert_select "osmChange node[id='#{node.id}'][version='1']", 0
1888     end
1889
1890     ##
1891     # test subscribe success
1892     def test_subscribe_success
1893       auth_header = basic_authorization_header create(:user).email, "test"
1894       changeset = create(:changeset, :closed)
1895
1896       assert_difference "changeset.subscribers.count", 1 do
1897         post changeset_subscribe_path(changeset), :headers => auth_header
1898       end
1899       assert_response :success
1900
1901       # not closed changeset
1902       changeset = create(:changeset)
1903       assert_difference "changeset.subscribers.count", 1 do
1904         post changeset_subscribe_path(changeset), :headers => auth_header
1905       end
1906       assert_response :success
1907     end
1908
1909     ##
1910     # test subscribe fail
1911     def test_subscribe_fail
1912       user = create(:user)
1913
1914       # unauthorized
1915       changeset = create(:changeset, :closed)
1916       assert_no_difference "changeset.subscribers.count" do
1917         post changeset_subscribe_path(changeset)
1918       end
1919       assert_response :unauthorized
1920
1921       auth_header = basic_authorization_header user.email, "test"
1922
1923       # bad changeset id
1924       assert_no_difference "changeset.subscribers.count" do
1925         post changeset_subscribe_path(:id => 999111), :headers => auth_header
1926       end
1927       assert_response :not_found
1928
1929       # trying to subscribe when already subscribed
1930       changeset = create(:changeset, :closed)
1931       changeset.subscribers.push(user)
1932       assert_no_difference "changeset.subscribers.count" do
1933         post changeset_subscribe_path(changeset), :headers => auth_header
1934       end
1935       assert_response :conflict
1936     end
1937
1938     ##
1939     # test unsubscribe success
1940     def test_unsubscribe_success
1941       user = create(:user)
1942       auth_header = basic_authorization_header user.email, "test"
1943       changeset = create(:changeset, :closed)
1944       changeset.subscribers.push(user)
1945
1946       assert_difference "changeset.subscribers.count", -1 do
1947         post changeset_unsubscribe_path(changeset), :headers => auth_header
1948       end
1949       assert_response :success
1950
1951       # not closed changeset
1952       changeset = create(:changeset)
1953       changeset.subscribers.push(user)
1954
1955       assert_difference "changeset.subscribers.count", -1 do
1956         post changeset_unsubscribe_path(changeset), :headers => auth_header
1957       end
1958       assert_response :success
1959     end
1960
1961     ##
1962     # test unsubscribe fail
1963     def test_unsubscribe_fail
1964       # unauthorized
1965       changeset = create(:changeset, :closed)
1966       assert_no_difference "changeset.subscribers.count" do
1967         post changeset_unsubscribe_path(changeset)
1968       end
1969       assert_response :unauthorized
1970
1971       auth_header = basic_authorization_header create(:user).email, "test"
1972
1973       # bad changeset id
1974       assert_no_difference "changeset.subscribers.count" do
1975         post changeset_unsubscribe_path(:id => 999111), :headers => auth_header
1976       end
1977       assert_response :not_found
1978
1979       # trying to unsubscribe when not subscribed
1980       changeset = create(:changeset, :closed)
1981       assert_no_difference "changeset.subscribers.count" do
1982         post changeset_unsubscribe_path(changeset), :headers => auth_header
1983       end
1984       assert_response :not_found
1985     end
1986
1987     private
1988
1989     ##
1990     # boilerplate for checking that certain changesets exist in the
1991     # output.
1992     def assert_changesets(changesets)
1993       assert_select "osm>changeset", changesets.size
1994       changesets.each do |changeset|
1995         assert_select "osm>changeset[id='#{changeset.id}']", 1
1996       end
1997     end
1998
1999     ##
2000     # update the changeset_id of a way element
2001     def update_changeset(xml, changeset_id)
2002       xml_attr_rewrite(xml, "changeset", changeset_id)
2003     end
2004
2005     ##
2006     # update an attribute in a way element
2007     def xml_attr_rewrite(xml, name, value)
2008       xml.find("//osm/way").first[name] = value.to_s
2009       xml
2010     end
2011
2012     ##
2013     # build XML for changesets
2014     def create_changeset_xml(user: nil, id: nil)
2015       root = XML::Document.new
2016       root.root = XML::Node.new "osm"
2017       cs = XML::Node.new "changeset"
2018       if user
2019         cs["user"] = user.display_name
2020         cs["uid"] = user.id.to_s
2021       end
2022       cs["id"] = id.to_s if id
2023       root.root << cs
2024       root
2025     end
2026   end
2027 end