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