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