]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets_controller_test.rb
802e006e1190e025d898d120d6964b81a817d384
[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     # when we make some simple changes we get the same changes back from the
1611     # diff download.
1612     def test_diff_download_simple
1613       node = create(:node)
1614
1615       ## First try with a non-public user, which should get a forbidden
1616       auth_header = basic_authorization_header create(:user, :data_public => false).email, "test"
1617
1618       # create a temporary changeset
1619       xml = "<osm><changeset>" \
1620             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1621             "</changeset></osm>"
1622       put changeset_create_path, :params => xml, :headers => auth_header
1623       assert_response :forbidden
1624
1625       ## Now try with a normal user
1626       auth_header = basic_authorization_header create(:user).email, "test"
1627
1628       # create a temporary changeset
1629       xml = "<osm><changeset>" \
1630             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1631             "</changeset></osm>"
1632       put changeset_create_path, :params => xml, :headers => auth_header
1633       assert_response :success
1634       changeset_id = @response.body.to_i
1635
1636       # add a diff to it
1637       diff = <<~CHANGESET
1638         <osmChange>
1639          <modify>
1640           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1641           <node id='#{node.id}' lon='1' lat='0' changeset='#{changeset_id}' version='2'/>
1642           <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset_id}' version='3'/>
1643           <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset_id}' version='4'/>
1644           <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset_id}' version='5'/>
1645           <node id='#{node.id}' lon='3' lat='2' changeset='#{changeset_id}' version='6'/>
1646           <node id='#{node.id}' lon='3' lat='3' changeset='#{changeset_id}' version='7'/>
1647           <node id='#{node.id}' lon='9' lat='9' changeset='#{changeset_id}' version='8'/>
1648          </modify>
1649         </osmChange>
1650       CHANGESET
1651
1652       # upload it
1653       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1654       assert_response :success,
1655                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1656
1657       get changeset_download_path(:id => changeset_id)
1658       assert_response :success
1659
1660       assert_select "osmChange", 1
1661       assert_select "osmChange>modify", 8
1662       assert_select "osmChange>modify>node", 8
1663     end
1664
1665     ##
1666     # culled this from josm to ensure that nothing in the way that josm
1667     # is formatting the request is causing it to fail.
1668     #
1669     # NOTE: the error turned out to be something else completely!
1670     def test_josm_upload
1671       auth_header = basic_authorization_header create(:user).email, "test"
1672
1673       # create a temporary changeset
1674       xml = "<osm><changeset>" \
1675             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1676             "</changeset></osm>"
1677       put changeset_create_path, :params => xml, :headers => auth_header
1678       assert_response :success
1679       changeset_id = @response.body.to_i
1680
1681       diff = <<~OSMFILE
1682         <osmChange version="0.6" generator="JOSM">
1683         <create version="0.6" generator="JOSM">
1684           <node id='-1' visible='true' changeset='#{changeset_id}' lat='51.49619982187321' lon='-0.18722061869438314' />
1685           <node id='-2' visible='true' changeset='#{changeset_id}' lat='51.496359883909605' lon='-0.18653093576241928' />
1686           <node id='-3' visible='true' changeset='#{changeset_id}' lat='51.49598132358285' lon='-0.18719613290981638' />
1687           <node id='-4' visible='true' changeset='#{changeset_id}' lat='51.4961591711078' lon='-0.18629015888084607' />
1688           <node id='-5' visible='true' changeset='#{changeset_id}' lat='51.49582126021711' lon='-0.18708186591517145' />
1689           <node id='-6' visible='true' changeset='#{changeset_id}' lat='51.49591018437858' lon='-0.1861432441734455' />
1690           <node id='-7' visible='true' changeset='#{changeset_id}' lat='51.49560784152179' lon='-0.18694719410005425' />
1691           <node id='-8' visible='true' changeset='#{changeset_id}' lat='51.49567389979617' lon='-0.1860289771788006' />
1692           <node id='-9' visible='true' changeset='#{changeset_id}' lat='51.49543761398892' lon='-0.186820684213126' />
1693           <way id='-10' action='modify' visible='true' changeset='#{changeset_id}'>
1694             <nd ref='-1' />
1695             <nd ref='-2' />
1696             <nd ref='-3' />
1697             <nd ref='-4' />
1698             <nd ref='-5' />
1699             <nd ref='-6' />
1700             <nd ref='-7' />
1701             <nd ref='-8' />
1702             <nd ref='-9' />
1703             <tag k='highway' v='residential' />
1704             <tag k='name' v='Foobar Street' />
1705           </way>
1706         </create>
1707         </osmChange>
1708       OSMFILE
1709
1710       # upload it
1711       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1712       assert_response :success,
1713                       "can't upload a diff from JOSM: #{@response.body}"
1714
1715       get changeset_download_path(:id => changeset_id)
1716       assert_response :success
1717
1718       assert_select "osmChange", 1
1719       assert_select "osmChange>create>node", 9
1720       assert_select "osmChange>create>way", 1
1721       assert_select "osmChange>create>way>nd", 9
1722       assert_select "osmChange>create>way>tag", 2
1723     end
1724
1725     ##
1726     # when we make some complex changes we get the same changes back from the
1727     # diff download.
1728     def test_diff_download_complex
1729       node = create(:node)
1730       node2 = create(:node)
1731       way = create(:way)
1732       auth_header = basic_authorization_header create(:user).email, "test"
1733
1734       # create a temporary changeset
1735       xml = "<osm><changeset>" \
1736             "<tag k='created_by' v='osm test suite checking changesets'/>" \
1737             "</changeset></osm>"
1738       put changeset_create_path, :params => xml, :headers => auth_header
1739       assert_response :success
1740       changeset_id = @response.body.to_i
1741
1742       # add a diff to it
1743       diff = <<~CHANGESET
1744         <osmChange>
1745          <delete>
1746           <node id='#{node.id}' lon='0' lat='0' changeset='#{changeset_id}' version='1'/>
1747          </delete>
1748          <create>
1749           <node id='-1' lon='9' lat='9' changeset='#{changeset_id}' version='0'/>
1750           <node id='-2' lon='8' lat='9' changeset='#{changeset_id}' version='0'/>
1751           <node id='-3' lon='7' lat='9' changeset='#{changeset_id}' version='0'/>
1752          </create>
1753          <modify>
1754           <node id='#{node2.id}' lon='20' lat='15' changeset='#{changeset_id}' version='1'/>
1755           <way id='#{way.id}' changeset='#{changeset_id}' version='1'>
1756            <nd ref='#{node2.id}'/>
1757            <nd ref='-1'/>
1758            <nd ref='-2'/>
1759            <nd ref='-3'/>
1760           </way>
1761          </modify>
1762         </osmChange>
1763       CHANGESET
1764
1765       # upload it
1766       post changeset_upload_path(:id => changeset_id), :params => diff, :headers => auth_header
1767       assert_response :success,
1768                       "can't upload multiple versions of an element in a diff: #{@response.body}"
1769
1770       get changeset_download_path(:id => changeset_id)
1771       assert_response :success
1772
1773       assert_select "osmChange", 1
1774       assert_select "osmChange>create", 3
1775       assert_select "osmChange>delete", 1
1776       assert_select "osmChange>modify", 2
1777       assert_select "osmChange>create>node", 3
1778       assert_select "osmChange>delete>node", 1
1779       assert_select "osmChange>modify>node", 1
1780       assert_select "osmChange>modify>way", 1
1781     end
1782
1783     def test_changeset_download
1784       changeset = create(:changeset)
1785       node = create(:node, :with_history, :version => 1, :changeset => changeset)
1786       tag = create(:old_node_tag, :old_node => node.old_nodes.find_by(:version => 1))
1787       node2 = create(:node, :with_history, :version => 1, :changeset => changeset)
1788       _node3 = create(:node, :with_history, :deleted, :version => 1, :changeset => changeset)
1789       _relation = create(:relation, :with_history, :version => 1, :changeset => changeset)
1790       _relation2 = create(:relation, :with_history, :deleted, :version => 1, :changeset => changeset)
1791
1792       get changeset_download_path(changeset)
1793
1794       assert_response :success
1795
1796       # FIXME: needs more assert_select tests
1797       assert_select "osmChange[version='#{Settings.api_version}'][generator='#{Settings.generator}']" do
1798         assert_select "create", :count => 5
1799         assert_select "create>node[id='#{node.id}'][visible='#{node.visible?}'][version='#{node.version}']" do
1800           assert_select "tag[k='#{tag.k}'][v='#{tag.v}']"
1801         end
1802         assert_select "create>node[id='#{node2.id}']"
1803       end
1804     end
1805
1806     ##
1807     # check that the bounding box of a changeset gets updated correctly
1808     # FIXME: This should really be moded to a integration test due to the with_controller
1809     def test_changeset_bbox
1810       way = create(:way)
1811       create(:way_node, :way => way, :node => create(:node, :lat => 3, :lon => 3))
1812
1813       auth_header = basic_authorization_header create(:user).email, "test"
1814
1815       # create a new changeset
1816       xml = "<osm><changeset/></osm>"
1817       put changeset_create_path, :params => xml, :headers => auth_header
1818       assert_response :success, "Creating of changeset failed."
1819       changeset_id = @response.body.to_i
1820
1821       # add a single node to it
1822       with_controller(NodesController.new) do
1823         xml = "<osm><node lon='1' lat='2' changeset='#{changeset_id}'/></osm>"
1824         put node_create_path, :params => xml, :headers => auth_header
1825         assert_response :success, "Couldn't create node."
1826       end
1827
1828       # get the bounding box back from the changeset
1829       get changeset_show_path(:id => changeset_id)
1830       assert_response :success, "Couldn't read back changeset."
1831       assert_select "osm>changeset[min_lon='1.0000000']", 1
1832       assert_select "osm>changeset[max_lon='1.0000000']", 1
1833       assert_select "osm>changeset[min_lat='2.0000000']", 1
1834       assert_select "osm>changeset[max_lat='2.0000000']", 1
1835
1836       # add another node to it
1837       with_controller(NodesController.new) do
1838         xml = "<osm><node lon='2' lat='1' changeset='#{changeset_id}'/></osm>"
1839         put node_create_path, :params => xml, :headers => auth_header
1840         assert_response :success, "Couldn't create second node."
1841       end
1842
1843       # get the bounding box back from the changeset
1844       get changeset_show_path(:id => changeset_id)
1845       assert_response :success, "Couldn't read back changeset for the second time."
1846       assert_select "osm>changeset[min_lon='1.0000000']", 1
1847       assert_select "osm>changeset[max_lon='2.0000000']", 1
1848       assert_select "osm>changeset[min_lat='1.0000000']", 1
1849       assert_select "osm>changeset[max_lat='2.0000000']", 1
1850
1851       # add (delete) a way to it, which contains a point at (3,3)
1852       with_controller(WaysController.new) do
1853         xml = update_changeset(xml_for_way(way), changeset_id)
1854         delete api_way_path(way), :params => xml.to_s, :headers => auth_header
1855         assert_response :success, "Couldn't delete a way."
1856       end
1857
1858       # get the bounding box back from the changeset
1859       get changeset_show_path(:id => changeset_id)
1860       assert_response :success, "Couldn't read back changeset for the third time."
1861       assert_select "osm>changeset[min_lon='1.0000000']", 1
1862       assert_select "osm>changeset[max_lon='3.0000000']", 1
1863       assert_select "osm>changeset[min_lat='1.0000000']", 1
1864       assert_select "osm>changeset[max_lat='3.0000000']", 1
1865     end
1866
1867     ##
1868     # test the query functionality of changesets
1869     def test_query
1870       private_user = create(:user, :data_public => false)
1871       private_user_changeset = create(:changeset, :user => private_user)
1872       private_user_closed_changeset = create(:changeset, :closed, :user => private_user)
1873       user = create(:user)
1874       changeset = create(:changeset, :user => user)
1875       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))
1876       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)
1877       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)
1878
1879       get changesets_path(:bbox => "-10,-10, 10, 10")
1880       assert_response :success, "can't get changesets in bbox"
1881       assert_changesets [changeset2, changeset3]
1882
1883       get changesets_path(:bbox => "4.5,4.5,4.6,4.6")
1884       assert_response :success, "can't get changesets in bbox"
1885       assert_changesets [changeset3]
1886
1887       # not found when looking for changesets of non-existing users
1888       get changesets_path(:user => User.maximum(:id) + 1)
1889       assert_response :not_found
1890       assert_equal "text/plain", @response.media_type
1891       get changesets_path(:display_name => " ")
1892       assert_response :not_found
1893       assert_equal "text/plain", @response.media_type
1894
1895       # can't get changesets of user 1 without authenticating
1896       get changesets_path(:user => private_user.id)
1897       assert_response :not_found, "shouldn't be able to get changesets by non-public user (ID)"
1898       get changesets_path(:display_name => private_user.display_name)
1899       assert_response :not_found, "shouldn't be able to get changesets by non-public user (name)"
1900
1901       # but this should work
1902       auth_header = basic_authorization_header private_user.email, "test"
1903       get changesets_path(:user => private_user.id), :headers => auth_header
1904       assert_response :success, "can't get changesets by user ID"
1905       assert_changesets [private_user_changeset, private_user_closed_changeset]
1906
1907       get changesets_path(:display_name => private_user.display_name), :headers => auth_header
1908       assert_response :success, "can't get changesets by user name"
1909       assert_changesets [private_user_changeset, private_user_closed_changeset]
1910
1911       # test json endpoint
1912       get changesets_path(:display_name => private_user.display_name), :headers => auth_header, :params => { :format => "json" }
1913       assert_response :success, "can't get changesets by user name"
1914
1915       js = ActiveSupport::JSON.decode(@response.body)
1916       assert_not_nil js
1917
1918       assert_equal Settings.api_version, js["version"]
1919       assert_equal Settings.generator, js["generator"]
1920       assert_equal 2, js["changesets"].count
1921
1922       # check that the correct error is given when we provide both UID and name
1923       get changesets_path(:user => private_user.id,
1924                           :display_name => private_user.display_name), :headers => auth_header
1925       assert_response :bad_request, "should be a bad request to have both ID and name specified"
1926
1927       get changesets_path(:user => private_user.id, :open => true), :headers => auth_header
1928       assert_response :success, "can't get changesets by user and open"
1929       assert_changesets [private_user_changeset]
1930
1931       get changesets_path(:time => "2007-12-31"), :headers => auth_header
1932       assert_response :success, "can't get changesets by time-since"
1933       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1934
1935       get changesets_path(:time => "2008-01-01T12:34Z"), :headers => auth_header
1936       assert_response :success, "can't get changesets by time-since with hour"
1937       assert_changesets [private_user_changeset, private_user_closed_changeset, changeset, closed_changeset, changeset2, changeset3]
1938
1939       get changesets_path(:time => "2007-12-31T23:59Z,2008-01-02T00:01Z"), :headers => auth_header
1940       assert_response :success, "can't get changesets by time-range"
1941       assert_changesets [closed_changeset]
1942
1943       get changesets_path(:open => "true"), :headers => auth_header
1944       assert_response :success, "can't get changesets by open-ness"
1945       assert_changesets [private_user_changeset, changeset, changeset2, changeset3]
1946
1947       get changesets_path(:closed => "true"), :headers => auth_header
1948       assert_response :success, "can't get changesets by closed-ness"
1949       assert_changesets [private_user_closed_changeset, closed_changeset]
1950
1951       get changesets_path(:closed => "true", :user => private_user.id), :headers => auth_header
1952       assert_response :success, "can't get changesets by closed-ness and user"
1953       assert_changesets [private_user_closed_changeset]
1954
1955       get changesets_path(:closed => "true", :user => user.id), :headers => auth_header
1956       assert_response :success, "can't get changesets by closed-ness and user"
1957       assert_changesets [closed_changeset]
1958
1959       get changesets_path(:changesets => "#{private_user_changeset.id},#{changeset.id},#{closed_changeset.id}"), :headers => auth_header
1960       assert_response :success, "can't get changesets by id (as comma-separated string)"
1961       assert_changesets [private_user_changeset, changeset, closed_changeset]
1962
1963       get changesets_path(:changesets => ""), :headers => auth_header
1964       assert_response :bad_request, "should be a bad request since changesets is empty"
1965     end
1966
1967     ##
1968     # test the query functionality of changesets with the limit parameter
1969     def test_query_limit
1970       user = create(:user)
1971       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))
1972       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))
1973       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))
1974       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))
1975       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))
1976
1977       get changesets_path
1978       assert_response :success
1979       assert_changesets_in_order [changeset5, changeset4, changeset3, changeset2, changeset1]
1980
1981       get changesets_path(:limit => "3")
1982       assert_response :success
1983       assert_changesets_in_order [changeset5, changeset4, changeset3]
1984
1985       get changesets_path(:limit => "0")
1986       assert_response :bad_request
1987
1988       get changesets_path(:limit => Settings.max_changeset_query_limit)
1989       assert_response :success
1990       assert_changesets_in_order [changeset5, changeset4, changeset3, changeset2, changeset1]
1991
1992       get changesets_path(:limit => Settings.max_changeset_query_limit + 1)
1993       assert_response :bad_request
1994     end
1995
1996     ##
1997     # test the query functionality of sequential changesets with order and time parameters
1998     def test_query_order
1999       user = create(:user)
2000       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))
2001       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))
2002       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))
2003       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))
2004       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))
2005       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))
2006
2007       get changesets_path
2008       assert_response :success
2009       assert_changesets_in_order [changeset6, changeset5, changeset4, changeset3, changeset2, changeset1]
2010
2011       get changesets_path(:order => "oldest")
2012       assert_response :success
2013       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4, changeset5, changeset6]
2014
2015       [
2016         # lower time bound at the opening time of a changeset
2017         ["2008-02-01T00:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3, changeset2]],
2018         # lower time bound in the middle of a changeset
2019         ["2008-02-01T12:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3]],
2020         # lower time bound at the closing time of a changeset
2021         ["2008-02-02T00:00:00Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3, changeset2], [changeset5, changeset4, changeset3]],
2022         # lower time bound after the closing time of a changeset
2023         ["2008-02-02T00:00:01Z", "2008-05-15T00:00:00Z", [changeset5, changeset4, changeset3], [changeset5, changeset4, changeset3]],
2024         # upper time bound in the middle of a changeset
2025         ["2007-09-09T12:00:00Z", "2008-04-01T12:00:00Z", [changeset4, changeset3, changeset2, changeset1], [changeset4, changeset3, changeset2, changeset1]],
2026         # empty range
2027         ["2009-02-02T00:00:01Z", "2018-05-15T00:00:00Z", [], []]
2028       ].each do |from, to, interval_changesets, point_changesets|
2029         get changesets_path(:time => "#{from},#{to}")
2030         assert_response :success
2031         assert_changesets_in_order interval_changesets
2032
2033         get changesets_path(:from => from, :to => to)
2034         assert_response :success
2035         assert_changesets_in_order point_changesets
2036
2037         get changesets_path(:from => from, :to => to, :order => "oldest")
2038         assert_response :success
2039         assert_changesets_in_order point_changesets.reverse
2040       end
2041     end
2042
2043     ##
2044     # test the query functionality of overlapping changesets with order and time parameters
2045     def test_query_order_overlapping
2046       user = create(:user)
2047       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))
2048       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))
2049       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))
2050       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))
2051       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))
2052
2053       get changesets_path(:time => "2015-06-04T00:00:00Z")
2054       assert_response :success
2055       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4]
2056
2057       get changesets_path(:from => "2015-06-04T00:00:00Z")
2058       assert_response :success
2059       assert_changesets_in_order [changeset1, changeset2, changeset3]
2060
2061       get changesets_path(:from => "2015-06-04T00:00:00Z", :order => "oldest")
2062       assert_response :success
2063       assert_changesets_in_order [changeset3, changeset2, changeset1]
2064
2065       get changesets_path(:time => "2015-06-04T16:00:00Z,2015-06-04T17:30:00Z")
2066       assert_response :success
2067       assert_changesets_in_order [changeset1, changeset2, changeset3, changeset4]
2068
2069       get changesets_path(:from => "2015-06-04T16:00:00Z", :to => "2015-06-04T17:30:00Z")
2070       assert_response :success
2071       assert_changesets_in_order [changeset1, changeset2]
2072
2073       get changesets_path(:from => "2015-06-04T16:00:00Z", :to => "2015-06-04T17:30:00Z", :order => "oldest")
2074       assert_response :success
2075       assert_changesets_in_order [changeset2, changeset1]
2076     end
2077
2078     ##
2079     # check that errors are returned if garbage is inserted
2080     # into query strings
2081     def test_query_invalid
2082       ["abracadabra!",
2083        "1,2,3,F",
2084        ";drop table users;"].each do |bbox|
2085         get changesets_path(:bbox => bbox)
2086         assert_response :bad_request, "'#{bbox}' isn't a bbox"
2087       end
2088
2089       ["now()",
2090        "00-00-00",
2091        ";drop table users;",
2092        ",",
2093        "-,-"].each do |time|
2094         get changesets_path(:time => time)
2095         assert_response :bad_request, "'#{time}' isn't a valid time range"
2096       end
2097
2098       ["me",
2099        "foobar",
2100        "-1",
2101        "0"].each do |uid|
2102         get changesets_path(:user => uid)
2103         assert_response :bad_request, "'#{uid}' isn't a valid user ID"
2104       end
2105
2106       get changesets_path(:order => "oldest", :time => "2008-01-01T00:00Z,2018-01-01T00:00Z")
2107       assert_response :bad_request, "cannot use order=oldest with time"
2108     end
2109
2110     ##
2111     # check updating tags on a changeset
2112     def test_changeset_update
2113       private_user = create(:user, :data_public => false)
2114       private_changeset = create(:changeset, :user => private_user)
2115       user = create(:user)
2116       changeset = create(:changeset, :user => user)
2117
2118       ## First try with a non-public user
2119       new_changeset = create_changeset_xml(:user => private_user)
2120       new_tag = XML::Node.new "tag"
2121       new_tag["k"] = "tagtesting"
2122       new_tag["v"] = "valuetesting"
2123       new_changeset.find("//osm/changeset").first << new_tag
2124
2125       # try without any authorization
2126       put changeset_show_path(private_changeset), :params => new_changeset.to_s
2127       assert_response :unauthorized
2128
2129       # try with the wrong authorization
2130       auth_header = basic_authorization_header create(:user).email, "test"
2131       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2132       assert_response :conflict
2133
2134       # now this should get an unauthorized
2135       auth_header = basic_authorization_header private_user.email, "test"
2136       put changeset_show_path(private_changeset), :params => new_changeset.to_s, :headers => auth_header
2137       assert_require_public_data "user with their data non-public, shouldn't be able to edit their changeset"
2138
2139       ## Now try with the public user
2140       new_changeset = create_changeset_xml(:id => 1)
2141       new_tag = XML::Node.new "tag"
2142       new_tag["k"] = "tagtesting"
2143       new_tag["v"] = "valuetesting"
2144       new_changeset.find("//osm/changeset").first << new_tag
2145
2146       # try without any authorization
2147       put changeset_show_path(changeset), :params => new_changeset.to_s
2148       assert_response :unauthorized
2149
2150       # try with the wrong authorization
2151       auth_header = basic_authorization_header create(:user).email, "test"
2152       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2153       assert_response :conflict
2154
2155       # now this should work...
2156       auth_header = basic_authorization_header user.email, "test"
2157       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2158       assert_response :success
2159
2160       assert_select "osm>changeset[id='#{changeset.id}']", 1
2161       assert_select "osm>changeset>tag", 1
2162       assert_select "osm>changeset>tag[k='tagtesting'][v='valuetesting']", 1
2163     end
2164
2165     ##
2166     # check that a user different from the one who opened the changeset
2167     # can't modify it.
2168     def test_changeset_update_invalid
2169       auth_header = basic_authorization_header create(:user).email, "test"
2170
2171       changeset = create(:changeset)
2172       new_changeset = create_changeset_xml(:user => changeset.user, :id => changeset.id)
2173       new_tag = XML::Node.new "tag"
2174       new_tag["k"] = "testing"
2175       new_tag["v"] = "testing"
2176       new_changeset.find("//osm/changeset").first << new_tag
2177
2178       put changeset_show_path(changeset), :params => new_changeset.to_s, :headers => auth_header
2179       assert_response :conflict
2180     end
2181
2182     ##
2183     # check that a changeset can contain a certain max number of changes.
2184     ## FIXME should be changed to an integration test due to the with_controller
2185     def test_changeset_limits
2186       auth_header = basic_authorization_header create(:user).email, "test"
2187
2188       # open a new changeset
2189       xml = "<osm><changeset/></osm>"
2190       put changeset_create_path, :params => xml, :headers => auth_header
2191       assert_response :success, "can't create a new changeset"
2192       cs_id = @response.body.to_i
2193
2194       # start the counter just short of where the changeset should finish.
2195       offset = 10
2196       # alter the database to set the counter on the changeset directly,
2197       # otherwise it takes about 6 minutes to fill all of them.
2198       changeset = Changeset.find(cs_id)
2199       changeset.num_changes = Changeset::MAX_ELEMENTS - offset
2200       changeset.save!
2201
2202       with_controller(NodesController.new) do
2203         # create a new node
2204         xml = "<osm><node changeset='#{cs_id}' lat='0.0' lon='0.0'/></osm>"
2205         put node_create_path, :params => xml, :headers => auth_header
2206         assert_response :success, "can't create a new node"
2207         node_id = @response.body.to_i
2208
2209         get api_node_path(:id => node_id)
2210         assert_response :success, "can't read back new node"
2211         node_doc = XML::Parser.string(@response.body).parse
2212         node_xml = node_doc.find("//osm/node").first
2213
2214         # loop until we fill the changeset with nodes
2215         offset.times do |i|
2216           node_xml["lat"] = rand.to_s
2217           node_xml["lon"] = rand.to_s
2218           node_xml["version"] = (i + 1).to_s
2219
2220           put api_node_path(:id => node_id), :params => node_doc.to_s, :headers => auth_header
2221           assert_response :success, "attempt #{i} should have succeeded"
2222         end
2223
2224         # trying again should fail
2225         node_xml["lat"] = rand.to_s
2226         node_xml["lon"] = rand.to_s
2227         node_xml["version"] = offset.to_s
2228
2229         put api_node_path(:id => node_id), :params => node_doc.to_s, :headers => auth_header
2230         assert_response :conflict, "final attempt should have failed"
2231       end
2232
2233       changeset = Changeset.find(cs_id)
2234       assert_equal Changeset::MAX_ELEMENTS + 1, changeset.num_changes
2235
2236       # check that the changeset is now closed as well
2237       assert_not(changeset.open?,
2238                  "changeset should have been auto-closed by exceeding " \
2239                  "element limit.")
2240     end
2241
2242     ##
2243     # check that the changeset download for a changeset with a redacted
2244     # element in it doesn't contain that element.
2245     def test_diff_download_redacted
2246       changeset = create(:changeset)
2247       node = create(:node, :with_history, :version => 2, :changeset => changeset)
2248       node_v1 = node.old_nodes.find_by(:version => 1)
2249       node_v1.redact!(create(:redaction))
2250
2251       get changeset_download_path(changeset)
2252       assert_response :success
2253
2254       assert_select "osmChange", 1
2255       # this changeset contains the node in versions 1 & 2, but 1 should
2256       # be hidden.
2257       assert_select "osmChange node[id='#{node.id}']", 1
2258       assert_select "osmChange node[id='#{node.id}'][version='1']", 0
2259     end
2260
2261     ##
2262     # test subscribe success
2263     def test_subscribe_success
2264       auth_header = basic_authorization_header create(:user).email, "test"
2265       changeset = create(:changeset, :closed)
2266
2267       assert_difference "changeset.subscribers.count", 1 do
2268         post changeset_subscribe_path(changeset), :headers => auth_header
2269       end
2270       assert_response :success
2271
2272       # not closed changeset
2273       changeset = create(:changeset)
2274       assert_difference "changeset.subscribers.count", 1 do
2275         post changeset_subscribe_path(changeset), :headers => auth_header
2276       end
2277       assert_response :success
2278     end
2279
2280     ##
2281     # test subscribe fail
2282     def test_subscribe_fail
2283       user = create(:user)
2284
2285       # unauthorized
2286       changeset = create(:changeset, :closed)
2287       assert_no_difference "changeset.subscribers.count" do
2288         post changeset_subscribe_path(changeset)
2289       end
2290       assert_response :unauthorized
2291
2292       auth_header = basic_authorization_header user.email, "test"
2293
2294       # bad changeset id
2295       assert_no_difference "changeset.subscribers.count" do
2296         post changeset_subscribe_path(:id => 999111), :headers => auth_header
2297       end
2298       assert_response :not_found
2299
2300       # trying to subscribe when already subscribed
2301       changeset = create(:changeset, :closed)
2302       changeset.subscribers.push(user)
2303       assert_no_difference "changeset.subscribers.count" do
2304         post changeset_subscribe_path(changeset), :headers => auth_header
2305       end
2306       assert_response :conflict
2307     end
2308
2309     ##
2310     # test unsubscribe success
2311     def test_unsubscribe_success
2312       user = create(:user)
2313       auth_header = basic_authorization_header user.email, "test"
2314       changeset = create(:changeset, :closed)
2315       changeset.subscribers.push(user)
2316
2317       assert_difference "changeset.subscribers.count", -1 do
2318         post changeset_unsubscribe_path(changeset), :headers => auth_header
2319       end
2320       assert_response :success
2321
2322       # not closed changeset
2323       changeset = create(:changeset)
2324       changeset.subscribers.push(user)
2325
2326       assert_difference "changeset.subscribers.count", -1 do
2327         post changeset_unsubscribe_path(changeset), :headers => auth_header
2328       end
2329       assert_response :success
2330     end
2331
2332     ##
2333     # test unsubscribe fail
2334     def test_unsubscribe_fail
2335       # unauthorized
2336       changeset = create(:changeset, :closed)
2337       assert_no_difference "changeset.subscribers.count" do
2338         post changeset_unsubscribe_path(changeset)
2339       end
2340       assert_response :unauthorized
2341
2342       auth_header = basic_authorization_header create(:user).email, "test"
2343
2344       # bad changeset id
2345       assert_no_difference "changeset.subscribers.count" do
2346         post changeset_unsubscribe_path(:id => 999111), :headers => auth_header
2347       end
2348       assert_response :not_found
2349
2350       # trying to unsubscribe when not subscribed
2351       changeset = create(:changeset, :closed)
2352       assert_no_difference "changeset.subscribers.count" do
2353         post changeset_unsubscribe_path(changeset), :headers => auth_header
2354       end
2355       assert_response :not_found
2356     end
2357
2358     private
2359
2360     ##
2361     # check that certain changesets exist in the output
2362     def assert_changesets(changesets)
2363       assert_select "osm>changeset", changesets.size
2364       changesets.each do |changeset|
2365         assert_select "osm>changeset[id='#{changeset.id}']", 1
2366       end
2367     end
2368
2369     ##
2370     # check that certain changesets exist in the output in the specified order
2371     def assert_changesets_in_order(changesets)
2372       assert_select "osm>changeset", changesets.size
2373       changesets.each_with_index do |changeset, index|
2374         assert_select "osm>changeset:nth-child(#{index + 1})[id='#{changeset.id}']", 1
2375       end
2376     end
2377
2378     ##
2379     # update the changeset_id of a way element
2380     def update_changeset(xml, changeset_id)
2381       xml_attr_rewrite(xml, "changeset", changeset_id)
2382     end
2383
2384     ##
2385     # update an attribute in a way element
2386     def xml_attr_rewrite(xml, name, value)
2387       xml.find("//osm/way").first[name] = value.to_s
2388       xml
2389     end
2390
2391     ##
2392     # build XML for changesets
2393     def create_changeset_xml(user: nil, id: nil)
2394       root = XML::Document.new
2395       root.root = XML::Node.new "osm"
2396       cs = XML::Node.new "changeset"
2397       if user
2398         cs["user"] = user.display_name
2399         cs["uid"] = user.id.to_s
2400       end
2401       cs["id"] = id.to_s if id
2402       root.root << cs
2403       root
2404     end
2405   end
2406 end