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