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