]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/nodes_controller_test.rb
Lock changeset in api create element actions
[rails.git] / test / controllers / api / nodes_controller_test.rb
1 require "test_helper"
2 require_relative "elements_test_helper"
3
4 module Api
5   class NodesControllerTest < ActionDispatch::IntegrationTest
6     include ElementsTestHelper
7
8     ##
9     # test all routes which lead to this controller
10     def test_routes
11       assert_routing(
12         { :path => "/api/0.6/nodes", :method => :get },
13         { :controller => "api/nodes", :action => "index" }
14       )
15       assert_routing(
16         { :path => "/api/0.6/nodes.json", :method => :get },
17         { :controller => "api/nodes", :action => "index", :format => "json" }
18       )
19       assert_routing(
20         { :path => "/api/0.6/nodes", :method => :post },
21         { :controller => "api/nodes", :action => "create" }
22       )
23       assert_routing(
24         { :path => "/api/0.6/node/1", :method => :get },
25         { :controller => "api/nodes", :action => "show", :id => "1" }
26       )
27       assert_routing(
28         { :path => "/api/0.6/node/1.json", :method => :get },
29         { :controller => "api/nodes", :action => "show", :id => "1", :format => "json" }
30       )
31       assert_routing(
32         { :path => "/api/0.6/node/1", :method => :put },
33         { :controller => "api/nodes", :action => "update", :id => "1" }
34       )
35       assert_routing(
36         { :path => "/api/0.6/node/1", :method => :delete },
37         { :controller => "api/nodes", :action => "destroy", :id => "1" }
38       )
39
40       assert_recognizes(
41         { :controller => "api/nodes", :action => "create" },
42         { :path => "/api/0.6/node/create", :method => :put }
43       )
44     end
45
46     ##
47     # test fetching multiple nodes
48     def test_index
49       node1 = create(:node)
50       node2 = create(:node, :deleted)
51       node3 = create(:node)
52       node4 = create(:node, :with_history, :version => 2)
53       node5 = create(:node, :deleted, :with_history, :version => 2)
54
55       # check error when no parameter provided
56       get api_nodes_path
57       assert_response :bad_request
58
59       # check error when no parameter value provided
60       get api_nodes_path(:nodes => "")
61       assert_response :bad_request
62
63       # test a working call
64       get api_nodes_path(:nodes => "#{node1.id},#{node2.id},#{node3.id},#{node4.id},#{node5.id}")
65       assert_response :success
66       assert_select "osm" do
67         assert_select "node", :count => 5
68         assert_select "node[id='#{node1.id}'][visible='true']", :count => 1
69         assert_select "node[id='#{node2.id}'][visible='false']", :count => 1
70         assert_select "node[id='#{node3.id}'][visible='true']", :count => 1
71         assert_select "node[id='#{node4.id}'][visible='true']", :count => 1
72         assert_select "node[id='#{node5.id}'][visible='false']", :count => 1
73       end
74
75       # test a working call with json format
76       get api_nodes_path(:nodes => "#{node1.id},#{node2.id},#{node3.id},#{node4.id},#{node5.id}", :format => "json")
77
78       js = ActiveSupport::JSON.decode(@response.body)
79       assert_not_nil js
80       assert_equal 5, js["elements"].count
81       assert_equal(5, js["elements"].count { |a| a["type"] == "node" })
82       assert_equal(1, js["elements"].count { |a| a["id"] == node1.id && a["visible"].nil? })
83       assert_equal(1, js["elements"].count { |a| a["id"] == node2.id && a["visible"] == false })
84       assert_equal(1, js["elements"].count { |a| a["id"] == node3.id && a["visible"].nil? })
85       assert_equal(1, js["elements"].count { |a| a["id"] == node4.id && a["visible"].nil? })
86       assert_equal(1, js["elements"].count { |a| a["id"] == node5.id && a["visible"] == false })
87
88       # check error when a non-existent node is included
89       get api_nodes_path(:nodes => "#{node1.id},#{node2.id},#{node3.id},#{node4.id},#{node5.id},0")
90       assert_response :not_found
91     end
92
93     def test_create_when_unauthorized
94       with_unchanging_request do |_headers, changeset|
95         osm = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
96
97         post api_nodes_path, :params => osm
98
99         assert_response :unauthorized
100       end
101     end
102
103     def test_create_by_private_user
104       with_unchanging_request([:data_public => false]) do |headers, changeset|
105         osm = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
106
107         post api_nodes_path, :params => osm, :headers => headers
108
109         assert_require_public_data "node create did not return forbidden status"
110       end
111     end
112
113     def test_create
114       with_request do |headers, changeset|
115         lat = rand(-50..50) + rand
116         lon = rand(-50..50) + rand
117
118         assert_difference "Node.count", 1 do
119           osm = "<osm><node lat='#{lat}' lon='#{lon}' changeset='#{changeset.id}'/></osm>"
120
121           post api_nodes_path, :params => osm, :headers => headers
122
123           assert_response :success, "node upload did not return success status"
124         end
125
126         created_node_id = @response.body
127         node = Node.find(created_node_id)
128         assert_in_delta lat * 10000000, node.latitude, 1, "saved node does not match requested latitude"
129         assert_in_delta lon * 10000000, node.longitude, 1, "saved node does not match requested longitude"
130         assert_equal changeset.id, node.changeset_id, "saved node does not belong to changeset that it was created in"
131         assert node.visible, "saved node is not visible"
132
133         changeset.reload
134         assert_equal 1, changeset.num_changes
135         assert_predicate changeset, :num_type_changes_in_sync?
136         assert_equal 1, changeset.num_created_nodes
137       end
138     end
139
140     def test_create_in_missing_changeset
141       with_unchanging_request do |headers|
142         osm = "<osm><node lat='0' lon='0' changeset='0'/></osm>"
143
144         post api_nodes_path, :params => osm, :headers => headers
145
146         assert_response :conflict
147       end
148     end
149
150     def test_create_with_invalid_osm_structure
151       with_unchanging_request do |headers|
152         osm = "<create/>"
153
154         post api_nodes_path, :params => osm, :headers => headers
155
156         assert_response :bad_request, "node upload did not return bad_request status"
157         assert_equal "Cannot parse valid node from xml string <create/>. XML doesn't contain an osm/node element.", @response.body
158       end
159     end
160
161     def test_create_without_lat
162       with_unchanging_request do |headers, changeset|
163         osm = "<osm><node lon='3.23' changeset='#{changeset.id}'/></osm>"
164
165         post api_nodes_path, :params => osm, :headers => headers
166
167         assert_response :bad_request, "node upload did not return bad_request status"
168         assert_equal "Cannot parse valid node from xml string <node lon=\"3.23\" changeset=\"#{changeset.id}\"/>. lat missing", @response.body
169       end
170     end
171
172     def test_create_without_lon
173       with_unchanging_request do |headers, changeset|
174         osm = "<osm><node lat='3.434' changeset='#{changeset.id}'/></osm>"
175
176         post api_nodes_path, :params => osm, :headers => headers
177
178         assert_response :bad_request, "node upload did not return bad_request status"
179         assert_equal "Cannot parse valid node from xml string <node lat=\"3.434\" changeset=\"#{changeset.id}\"/>. lon missing", @response.body
180       end
181     end
182
183     def test_create_with_non_numeric_lat
184       with_unchanging_request do |headers, changeset|
185         osm = "<osm><node lat='abc' lon='3.23' changeset='#{changeset.id}'/></osm>"
186
187         post api_nodes_path, :params => osm, :headers => headers
188
189         assert_response :bad_request, "node upload did not return bad_request status"
190         assert_equal "Cannot parse valid node from xml string <node lat=\"abc\" lon=\"3.23\" changeset=\"#{changeset.id}\"/>. lat not a number", @response.body
191       end
192     end
193
194     def test_create_with_non_numeric_lon
195       with_unchanging_request do |headers, changeset|
196         osm = "<osm><node lat='3.434' lon='abc' changeset='#{changeset.id}'/></osm>"
197
198         post api_nodes_path, :params => osm, :headers => headers
199
200         assert_response :bad_request, "node upload did not return bad_request status"
201         assert_equal "Cannot parse valid node from xml string <node lat=\"3.434\" lon=\"abc\" changeset=\"#{changeset.id}\"/>. lon not a number", @response.body
202       end
203     end
204
205     def test_create_with_tag_too_long
206       with_unchanging_request do |headers, changeset|
207         osm = "<osm><node lat='3.434' lon='3.23' changeset='#{changeset.id}'><tag k='foo' v='#{'x' * 256}'/></node></osm>"
208
209         post api_nodes_path, :params => osm, :headers => headers
210
211         assert_response :bad_request, "node upload did not return bad_request status"
212         assert_match(/ v: is too long \(maximum is 255 characters\) /, @response.body)
213       end
214     end
215
216     ##
217     # try and put something into a string that the API might
218     # use unquoted and therefore allow code injection
219     def test_create_with_string_injection_by_private_user
220       with_unchanging_request([:data_public => false]) do |headers, changeset|
221         osm = <<~OSM
222           <osm>
223             <node lat='0' lon='0' changeset='#{changeset.id}'>
224               <tag k='\#{@user.inspect}' v='0'/>
225             </node>
226           </osm>
227         OSM
228
229         post api_nodes_path, :params => osm, :headers => headers
230
231         assert_require_public_data "Shouldn't be able to create with non-public user"
232       end
233     end
234
235     ##
236     # try and put something into a string that the API might
237     # use unquoted and therefore allow code injection
238     def test_create_with_string_injection
239       with_request do |headers, changeset|
240         assert_difference "Node.count", 1 do
241           osm = <<~OSM
242             <osm>
243               <node lat='0' lon='0' changeset='#{changeset.id}'>
244                 <tag k='\#{@user.inspect}' v='0'/>
245               </node>
246             </osm>
247           OSM
248
249           post api_nodes_path, :params => osm, :headers => headers
250
251           assert_response :success
252         end
253
254         created_node_id = @response.body
255         db_node = Node.find(created_node_id)
256
257         get api_node_path(created_node_id)
258
259         assert_response :success
260
261         api_node = Node.from_xml(@response.body)
262         assert_not_nil api_node, "downloaded node is nil, but shouldn't be"
263         assert_equal db_node.tags, api_node.tags, "tags are corrupted"
264         assert_includes api_node.tags, "\#{@user.inspect}"
265       end
266     end
267
268     def test_create_race_condition
269       user = create(:user)
270       changeset = create(:changeset, :user => user)
271       auth_header = bearer_authorization_header user
272       path = api_nodes_path
273       concurrency_level = 16
274
275       threads = Array.new(concurrency_level) do
276         Thread.new do
277           osm = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
278           post path, :params => osm, :headers => auth_header
279         end
280       end
281       threads.each(&:join)
282
283       changeset.reload
284       assert_equal concurrency_level, changeset.num_changes
285       assert_predicate changeset, :num_type_changes_in_sync?
286       assert_equal concurrency_level, changeset.num_created_nodes
287     end
288
289     def test_show_not_found
290       get api_node_path(0)
291       assert_response :not_found
292     end
293
294     def test_show_deleted
295       get api_node_path(create(:node, :deleted))
296       assert_response :gone
297     end
298
299     def test_show
300       node = create(:node, :timestamp => "2021-02-03T00:00:00Z")
301
302       get api_node_path(node)
303
304       assert_response :success
305       assert_not_nil @response.header["Last-Modified"]
306       assert_equal "2021-02-03T00:00:00Z", Time.parse(@response.header["Last-Modified"]).utc.xmlschema
307     end
308
309     def test_show_lat_lon_decimal_format
310       node = create(:node, :latitude => (0.00004 * OldNode::SCALE).to_i, :longitude => (0.00008 * OldNode::SCALE).to_i)
311
312       get api_node_path(node)
313
314       assert_match(/lat="0.0000400"/, response.body)
315       assert_match(/lon="0.0000800"/, response.body)
316     end
317
318     def test_destroy_when_unauthorized
319       with_unchanging(:node) do |node|
320         delete api_node_path(node)
321
322         assert_response :unauthorized
323       end
324     end
325
326     def test_destroy_in_closed_changeset_by_private_user
327       with_unchanging(:node) do |node|
328         with_unchanging_request([:data_public => false], [:closed]) do |headers, changeset|
329           osm_xml = xml_for_node node
330           osm_xml = update_changeset osm_xml, changeset.id
331
332           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
333
334           assert_require_public_data "non-public user shouldn't be able to delete node"
335         end
336       end
337     end
338
339     def test_destroy_in_missing_changeset_by_private_user
340       with_unchanging(:node) do |node|
341         with_unchanging_request([:data_public => false]) do |headers|
342           osm_xml = xml_for_node node
343           osm_xml = update_changeset osm_xml, 0
344
345           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
346
347           assert_require_public_data "shouldn't be able to delete node, when user's data is private"
348         end
349       end
350     end
351
352     def test_destroy_by_private_user
353       with_unchanging(:node) do |node|
354         with_unchanging_request([:data_public => false]) do |headers, changeset|
355           osm_xml = xml_for_node node
356           osm_xml = update_changeset osm_xml, changeset.id
357
358           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
359
360           assert_require_public_data "shouldn't be able to delete node when user's data isn't public'"
361         end
362       end
363     end
364
365     def test_destroy_deleted_node_by_private_user
366       with_unchanging(:node, :deleted) do |node|
367         with_unchanging_request([:data_public => false]) do |headers, changeset|
368           osm_xml = "<osm><node id='#{node.id}' changeset='#{changeset.id}' version='1' lat='0' lon='0'/></osm>"
369
370           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
371
372           assert_require_public_data
373         end
374       end
375     end
376
377     def test_destroy_missing_node_by_private_user
378       with_unchanging_request([:data_public => false]) do |headers|
379         delete api_node_path(0), :headers => headers
380
381         assert_require_public_data
382       end
383     end
384
385     def test_destroy_node_in_way_by_private_user
386       with_unchanging(:node) do |node|
387         create(:way_node, :node => node)
388
389         with_unchanging_request([:data_public => false]) do |headers, changeset|
390           osm_xml = xml_for_node node
391           osm_xml = update_changeset osm_xml, changeset.id
392
393           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
394
395           assert_require_public_data "shouldn't be able to delete a node used in a way (#{@response.body})"
396         end
397       end
398     end
399
400     def test_destroy_node_in_relation_by_private_user
401       with_unchanging(:node) do |node|
402         create(:relation_member, :member => node)
403
404         with_unchanging_request([:data_public => false]) do |headers, changeset|
405           osm_xml = xml_for_node node
406           osm_xml = update_changeset osm_xml, changeset.id
407
408           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
409
410           assert_require_public_data "shouldn't be able to delete a node used in a relation (#{@response.body})"
411         end
412       end
413     end
414
415     def test_destroy_in_closed_changeset
416       with_unchanging(:node) do |node|
417         with_unchanging_request([], [:closed]) do |headers, changeset|
418           osm_xml = xml_for_node node
419           osm_xml = update_changeset osm_xml, changeset.id
420
421           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
422
423           assert_response :conflict
424         end
425       end
426     end
427
428     def test_destroy_in_missing_changeset
429       with_unchanging(:node) do |node|
430         with_unchanging_request do |headers|
431           osm_xml = xml_for_node node
432           osm_xml = update_changeset osm_xml, 0
433
434           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
435
436           assert_response :conflict
437         end
438       end
439     end
440
441     def test_destroy_different_node
442       with_unchanging(:node) do |node|
443         with_unchanging(:node) do |other_node|
444           with_unchanging_request do |headers, changeset|
445             osm_xml = xml_for_node other_node
446             osm_xml = update_changeset osm_xml, changeset.id
447
448             delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
449
450             assert_response :bad_request, "should not be able to delete a node with a different ID from the XML"
451           end
452         end
453       end
454     end
455
456     def test_destroy_invalid_osm_structure
457       with_unchanging(:node) do |node|
458         with_unchanging_request do |headers|
459           osm = "<delete/>"
460
461           delete api_node_path(node), :params => osm, :headers => headers
462
463           assert_response :bad_request, "should not be able to delete a node without a valid XML payload"
464         end
465       end
466     end
467
468     def test_destroy
469       with_request do |headers, changeset|
470         node = create(:node)
471         osm_xml = xml_for_node node
472         osm_xml = update_changeset osm_xml, changeset.id
473
474         delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
475
476         assert_response :success
477
478         response_node_version = @response.body.to_i
479         assert_operator response_node_version, :>, node.version, "delete request should return a new version number for node"
480         node.reload
481         assert_not_predicate node, :visible?
482         assert_equal response_node_version, node.version
483
484         changeset.reload
485         assert_equal 1, changeset.num_changes
486         assert_predicate changeset, :num_type_changes_in_sync?
487         assert_equal 1, changeset.num_deleted_nodes
488       end
489     end
490
491     def test_destroy_twice
492       user = create(:user)
493       node = create(:node, :changeset => create(:changeset, :user => user))
494       osm_xml = xml_for_node node
495
496       delete api_node_path(node), :params => osm_xml.to_s, :headers => bearer_authorization_header(user)
497
498       assert_response :success
499
500       delete api_node_path(node), :params => osm_xml.to_s, :headers => bearer_authorization_header(user)
501
502       assert_response :gone
503     end
504
505     def test_destroy_deleted_node
506       with_unchanging(:node, :deleted) do |node|
507         with_unchanging_request do |headers, changeset|
508           osm = "<osm><node id='#{node.id}' changeset='#{changeset.id}' version='1' lat='0' lon='0'/></osm>"
509
510           delete api_node_path(node), :params => osm, :headers => headers
511
512           assert_response :gone
513         end
514       end
515     end
516
517     def test_destroy_missing_node
518       with_unchanging_request do |headers|
519         delete api_node_path(0), :headers => headers
520
521         assert_response :not_found
522       end
523     end
524
525     def test_destroy_node_in_ways
526       with_unchanging(:node) do |node|
527         way_node = create(:way_node, :node => node)
528         way_node2 = create(:way_node, :node => node)
529
530         with_unchanging_request do |headers, changeset|
531           osm_xml = xml_for_node node
532           osm_xml = update_changeset osm_xml, changeset.id
533
534           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
535
536           assert_response :precondition_failed, "shouldn't be able to delete a node used in a way (#{@response.body})"
537           assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way_node.way.id},#{way_node2.way.id}.", @response.body
538         end
539       end
540     end
541
542     def test_destroy_node_in_relations
543       with_unchanging(:node) do |node|
544         relation_member = create(:relation_member, :member => node)
545         relation_member2 = create(:relation_member, :member => node)
546
547         with_unchanging_request do |headers, changeset|
548           osm_xml = xml_for_node node
549           osm_xml = update_changeset osm_xml, changeset.id
550
551           delete api_node_path(node), :params => osm_xml.to_s, :headers => headers
552
553           assert_response :precondition_failed, "shouldn't be able to delete a node used in a relation (#{@response.body})"
554           assert_equal "Precondition failed: Node #{node.id} is still used by relations #{relation_member.relation.id},#{relation_member2.relation.id}.", @response.body
555         end
556       end
557     end
558
559     def test_update_when_unauthorized
560       with_unchanging(:node) do |node|
561         osm_xml = xml_for_node node
562
563         put api_node_path(node), :params => osm_xml.to_s
564
565         assert_response :unauthorized
566       end
567     end
568
569     def test_update_in_changeset_of_other_user_by_private_user
570       with_unchanging(:node) do |node|
571         other_user = create(:user)
572
573         with_unchanging_request([:data_public => false], [:user => other_user]) do |headers, changeset|
574           osm_xml = xml_for_node node
575           osm_xml = update_changeset osm_xml, changeset.id
576
577           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
578
579           assert_require_public_data "update with other user's changeset should be forbidden when data isn't public"
580         end
581       end
582     end
583
584     def test_update_in_closed_changeset_by_private_user
585       with_unchanging(:node) do |node|
586         with_unchanging_request([:data_public => false], [:closed]) do |headers, changeset|
587           osm_xml = xml_for_node node
588           osm_xml = update_changeset osm_xml, changeset.id
589
590           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
591
592           assert_require_public_data "update with closed changeset should be forbidden, when data isn't public"
593         end
594       end
595     end
596
597     def test_update_in_missing_changeset_by_private_user
598       with_unchanging(:node) do |node|
599         with_unchanging_request([:data_public => false]) do |headers|
600           osm_xml = xml_for_node node
601           osm_xml = update_changeset osm_xml, 0
602
603           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
604
605           assert_require_public_data "update with changeset=0 should be forbidden, when data isn't public"
606         end
607       end
608     end
609
610     def test_update_with_lat_too_large_by_private_user
611       check_update_with_invalid_attr_value "lat", 91.0, :data_public => false
612     end
613
614     def test_update_with_lat_too_small_by_private_user
615       check_update_with_invalid_attr_value "lat", -91.0, :data_public => false
616     end
617
618     def test_update_with_lon_too_large_by_private_user
619       check_update_with_invalid_attr_value "lon", 181.0, :data_public => false
620     end
621
622     def test_update_with_lon_too_small_by_private_user
623       check_update_with_invalid_attr_value "lon", -181.0, :data_public => false
624     end
625
626     def test_update_by_private_user
627       with_unchanging(:node) do |node|
628         with_unchanging_request([:data_public => false]) do |headers, changeset|
629           osm_xml = xml_for_node node
630           osm_xml = update_changeset osm_xml, changeset.id
631
632           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
633
634           assert_require_public_data "should have failed with a forbidden when data isn't public"
635         end
636       end
637     end
638
639     def test_update_in_changeset_of_other_user
640       with_unchanging(:node) do |node|
641         other_user = create(:user)
642
643         with_unchanging_request([], [:user => other_user]) do |headers, changeset|
644           osm_xml = xml_for_node node
645           osm_xml = update_changeset osm_xml, changeset.id
646
647           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
648
649           assert_response :conflict, "update with other user's changeset should be rejected"
650         end
651       end
652     end
653
654     def test_update_in_closed_changeset
655       with_unchanging(:node) do |node|
656         with_unchanging_request([], [:closed]) do |headers, changeset|
657           osm_xml = xml_for_node node
658           osm_xml = update_changeset osm_xml, changeset.id
659
660           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
661
662           assert_response :conflict, "update with closed changeset should be rejected"
663         end
664       end
665     end
666
667     def test_update_in_missing_changeset
668       with_unchanging(:node) do |node|
669         with_unchanging_request do |headers|
670           osm_xml = xml_for_node node
671           osm_xml = update_changeset osm_xml, 0
672
673           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
674
675           assert_response :conflict, "update with changeset=0 should be rejected"
676         end
677       end
678     end
679
680     def test_update_with_lat_too_large
681       check_update_with_invalid_attr_value "lat", 91.0
682     end
683
684     def test_update_with_lat_too_small
685       check_update_with_invalid_attr_value "lat", -91.0
686     end
687
688     def test_update_with_lon_too_large
689       check_update_with_invalid_attr_value "lon", 181.0
690     end
691
692     def test_update_with_lon_too_small
693       check_update_with_invalid_attr_value "lon", -181.0
694     end
695
696     def test_update_with_version_behind
697       with_unchanging(:node, :version => 2) do |node|
698         with_unchanging_request do |headers, changeset|
699           osm_xml = xml_for_node node
700           osm_xml = xml_attr_rewrite osm_xml, "version", node.version - 1
701           osm_xml = update_changeset osm_xml, changeset.id
702
703           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
704
705           assert_response :conflict, "should have failed on old version number"
706         end
707       end
708     end
709
710     def test_update_with_version_ahead
711       with_unchanging(:node, :version => 2) do |node|
712         with_unchanging_request do |headers, changeset|
713           osm_xml = xml_for_node node
714           osm_xml = xml_attr_rewrite osm_xml, "version", node.version + 1
715           osm_xml = update_changeset osm_xml, changeset.id
716
717           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
718
719           assert_response :conflict, "should have failed on skipped version number"
720         end
721       end
722     end
723
724     def test_update_with_invalid_version
725       with_unchanging(:node) do |node|
726         with_unchanging_request do |headers, changeset|
727           osm_xml = xml_for_node node
728           osm_xml = xml_attr_rewrite osm_xml, "version", "p1r4t3s!"
729           osm_xml = update_changeset osm_xml, changeset.id
730
731           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
732
733           assert_response :conflict, "should not be able to put 'p1r4at3s!' in the version field"
734         end
735       end
736     end
737
738     def test_update_other_node
739       with_unchanging(:node) do |node|
740         with_unchanging(:node) do |other_node|
741           with_unchanging_request do |headers, changeset|
742             osm_xml = xml_for_node other_node
743             osm_xml = update_changeset osm_xml, changeset.id
744
745             put api_node_path(node), :params => osm_xml.to_s, :headers => headers
746
747             assert_response :bad_request, "should not be able to update a node with a different ID from the XML"
748           end
749         end
750       end
751     end
752
753     def test_update_with_invalid_osm_structure
754       with_unchanging(:node) do |node|
755         with_unchanging_request do |headers|
756           osm = "<update/>"
757
758           put api_node_path(node), :params => osm, :headers => headers
759
760           assert_response :bad_request, "should not be able to update a node with non-OSM XML doc."
761         end
762       end
763     end
764
765     def test_update
766       with_request do |headers, changeset|
767         node = create(:node)
768         osm_xml = xml_for_node node
769         osm_xml = update_changeset osm_xml, changeset.id
770
771         put api_node_path(node), :params => osm_xml.to_s, :headers => headers
772
773         assert_response :success, "a valid update request failed"
774
775         changeset.reload
776         assert_equal 1, changeset.num_changes
777         assert_predicate changeset, :num_type_changes_in_sync?
778         assert_equal 1, changeset.num_modified_nodes
779       end
780     end
781
782     def test_update_with_duplicate_tags
783       with_unchanging(:node) do |node|
784         create(:node_tag, :node => node, :k => "key_to_duplicate", :v => "value_to_duplicate")
785
786         with_unchanging_request do |headers, changeset|
787           tag_xml = XML::Node.new("tag")
788           tag_xml["k"] = "key_to_duplicate"
789           tag_xml["v"] = "value_to_duplicate"
790
791           osm_xml = xml_for_node node
792           osm_xml.find("//osm/node").first << tag_xml
793           osm_xml = update_changeset osm_xml, changeset.id
794
795           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
796
797           assert_response :bad_request, "adding duplicate tags to a node should fail with 'bad request'"
798           assert_equal "Element node/#{node.id} has duplicate tags with key key_to_duplicate", @response.body
799         end
800       end
801     end
802
803     ##
804     # test initial rate limit
805     def test_initial_rate_limit
806       # create a user
807       user = create(:user)
808
809       # create a changeset that puts us near the initial rate limit
810       changeset = create(:changeset, :user => user,
811                                      :created_at => Time.now.utc - 5.minutes,
812                                      :num_changes => Settings.initial_changes_per_hour - 1)
813
814       # create authentication header
815       auth_header = bearer_authorization_header user
816
817       # try creating a node
818       xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
819       post api_nodes_path, :params => xml, :headers => auth_header
820       assert_response :success, "node create did not return success status"
821
822       # get the id of the node we created
823       nodeid = @response.body
824
825       # try updating the node, which should be rate limited
826       xml = "<osm><node id='#{nodeid}' version='1' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
827       put api_node_path(nodeid), :params => xml, :headers => auth_header
828       assert_response :too_many_requests, "node update did not hit rate limit"
829
830       # try deleting the node, which should be rate limited
831       xml = "<osm><node id='#{nodeid}' version='2' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
832       delete api_node_path(nodeid), :params => xml, :headers => auth_header
833       assert_response :too_many_requests, "node delete did not hit rate limit"
834
835       # try creating a node, which should be rate limited
836       xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
837       post api_nodes_path, :params => xml, :headers => auth_header
838       assert_response :too_many_requests, "node create did not hit rate limit"
839     end
840
841     ##
842     # test maximum rate limit
843     def test_maximum_rate_limit
844       # create a user
845       user = create(:user)
846
847       # create a changeset to establish our initial edit time
848       changeset = create(:changeset, :user => user,
849                                      :created_at => Time.now.utc - 28.days)
850
851       # create changeset to put us near the maximum rate limit
852       total_changes = Settings.max_changes_per_hour - 1
853       while total_changes.positive?
854         changes = [total_changes, Changeset::MAX_ELEMENTS].min
855         changeset = create(:changeset, :user => user,
856                                        :created_at => Time.now.utc - 5.minutes,
857                                        :num_changes => changes)
858         total_changes -= changes
859       end
860
861       # create authentication header
862       auth_header = bearer_authorization_header user
863
864       # try creating a node
865       xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
866       post api_nodes_path, :params => xml, :headers => auth_header
867       assert_response :success, "node create did not return success status"
868
869       # get the id of the node we created
870       nodeid = @response.body
871
872       # try updating the node, which should be rate limited
873       xml = "<osm><node id='#{nodeid}' version='1' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
874       put api_node_path(nodeid), :params => xml, :headers => auth_header
875       assert_response :too_many_requests, "node update did not hit rate limit"
876
877       # try deleting the node, which should be rate limited
878       xml = "<osm><node id='#{nodeid}' version='2' lat='1' lon='1' changeset='#{changeset.id}'/></osm>"
879       delete api_node_path(nodeid), :params => xml, :headers => auth_header
880       assert_response :too_many_requests, "node delete did not hit rate limit"
881
882       # try creating a node, which should be rate limited
883       xml = "<osm><node lat='0' lon='0' changeset='#{changeset.id}'/></osm>"
884       post api_nodes_path, :params => xml, :headers => auth_header
885       assert_response :too_many_requests, "node create did not hit rate limit"
886     end
887
888     private
889
890     def check_update_with_invalid_attr_value(name, value, data_public: true)
891       with_unchanging(:node) do |node|
892         with_unchanging_request([:data_public => data_public]) do |headers, changeset|
893           osm_xml = xml_for_node node
894           osm_xml = xml_attr_rewrite osm_xml, name, value
895           osm_xml = update_changeset osm_xml, changeset.id
896
897           put api_node_path(node), :params => osm_xml.to_s, :headers => headers
898
899           if data_public
900             assert_response :bad_request, "node at #{name}=#{value} should be rejected"
901           else
902             assert_require_public_data "node at #{name}=#{value} should be forbidden, when data isn't public"
903           end
904         end
905       end
906     end
907
908     def affected_models
909       [Node, NodeTag,
910        OldNode, OldNodeTag]
911     end
912
913     ##
914     # update an attribute in the node element
915     def xml_attr_rewrite(xml, name, value)
916       xml.find("//osm/node").first[name] = value.to_s
917       xml
918     end
919   end
920 end