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