]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/ways_controller_test.rb
Add frozen_string_literal comments to ruby files
[rails.git] / test / controllers / api / ways_controller_test.rb
1 # frozen_string_literal: true
2
3 require "test_helper"
4 require_relative "elements_test_helper"
5
6 module Api
7   class WaysControllerTest < ActionDispatch::IntegrationTest
8     include ElementsTestHelper
9
10     ##
11     # test all routes which lead to this controller
12     def test_routes
13       assert_routing(
14         { :path => "/api/0.6/ways", :method => :get },
15         { :controller => "api/ways", :action => "index" }
16       )
17       assert_routing(
18         { :path => "/api/0.6/ways.json", :method => :get },
19         { :controller => "api/ways", :action => "index", :format => "json" }
20       )
21       assert_routing(
22         { :path => "/api/0.6/ways", :method => :post },
23         { :controller => "api/ways", :action => "create" }
24       )
25       assert_routing(
26         { :path => "/api/0.6/way/1", :method => :get },
27         { :controller => "api/ways", :action => "show", :id => "1" }
28       )
29       assert_routing(
30         { :path => "/api/0.6/way/1.json", :method => :get },
31         { :controller => "api/ways", :action => "show", :id => "1", :format => "json" }
32       )
33       assert_routing(
34         { :path => "/api/0.6/way/1/full", :method => :get },
35         { :controller => "api/ways", :action => "show", :full => true, :id => "1" }
36       )
37       assert_routing(
38         { :path => "/api/0.6/way/1/full.json", :method => :get },
39         { :controller => "api/ways", :action => "show", :full => true, :id => "1", :format => "json" }
40       )
41       assert_routing(
42         { :path => "/api/0.6/way/1", :method => :put },
43         { :controller => "api/ways", :action => "update", :id => "1" }
44       )
45       assert_routing(
46         { :path => "/api/0.6/way/1", :method => :delete },
47         { :controller => "api/ways", :action => "destroy", :id => "1" }
48       )
49
50       assert_recognizes(
51         { :controller => "api/ways", :action => "create" },
52         { :path => "/api/0.6/way/create", :method => :put }
53       )
54     end
55
56     ##
57     # test fetching multiple ways
58     def test_index
59       way1 = create(:way)
60       way2 = create(:way, :deleted)
61       way3 = create(:way)
62       way4 = create(:way)
63
64       # check error when no parameter provided
65       get api_ways_path
66       assert_response :bad_request
67
68       # check error when no parameter value provided
69       get api_ways_path(:ways => "")
70       assert_response :bad_request
71
72       # test a working call
73       get api_ways_path(:ways => "#{way1.id},#{way2.id},#{way3.id},#{way4.id}")
74       assert_response :success
75       assert_select "osm" do
76         assert_select "way", :count => 4
77         assert_select "way[id='#{way1.id}'][visible='true']", :count => 1
78         assert_select "way[id='#{way2.id}'][visible='false']", :count => 1
79         assert_select "way[id='#{way3.id}'][visible='true']", :count => 1
80         assert_select "way[id='#{way4.id}'][visible='true']", :count => 1
81       end
82
83       # test a working call with json format
84       get api_ways_path(:ways => "#{way1.id},#{way2.id},#{way3.id},#{way4.id}", :format => "json")
85
86       js = ActiveSupport::JSON.decode(@response.body)
87       assert_not_nil js
88       assert_equal 4, js["elements"].count
89       assert_equal(4, js["elements"].count { |a| a["type"] == "way" })
90       assert_equal(1, js["elements"].count { |a| a["id"] == way1.id && a["visible"].nil? })
91       assert_equal(1, js["elements"].count { |a| a["id"] == way2.id && a["visible"] == false })
92       assert_equal(1, js["elements"].count { |a| a["id"] == way3.id && a["visible"].nil? })
93       assert_equal(1, js["elements"].count { |a| a["id"] == way4.id && a["visible"].nil? })
94
95       # check error when a non-existent way is included
96       get api_ways_path(:ways => "#{way1.id},#{way2.id},#{way3.id},#{way4.id},0")
97       assert_response :not_found
98     end
99
100     # -------------------------------------
101     # Test showing ways.
102     # -------------------------------------
103
104     def test_show_not_found
105       get api_way_path(0)
106       assert_response :not_found
107     end
108
109     def test_show_deleted
110       get api_way_path(create(:way, :deleted))
111       assert_response :gone
112     end
113
114     def test_show
115       way = create(:way, :timestamp => "2021-02-03T00:00:00Z")
116       node = create(:node, :timestamp => "2021-04-05T00:00:00Z")
117       create(:way_node, :way => way, :node => node)
118
119       get api_way_path(way)
120
121       assert_response :success
122       assert_not_nil @response.header["Last-Modified"]
123       assert_equal "2021-02-03T00:00:00Z", Time.parse(@response.header["Last-Modified"]).utc.xmlschema
124     end
125
126     def test_show_json
127       way = create(:way_with_nodes, :nodes_count => 3)
128
129       get api_way_path(way, :format => "json")
130
131       assert_response :success
132
133       js = ActiveSupport::JSON.decode(@response.body)
134       assert_not_nil js
135       assert_equal 1, js["elements"].count
136       js_ways = js["elements"].filter { |e| e["type"] == "way" }
137       assert_equal 1, js_ways.count
138       assert_equal way.id, js_ways[0]["id"]
139       assert_equal 1, js_ways[0]["version"]
140     end
141
142     ##
143     # check the "full" mode
144     def test_show_full
145       way = create(:way_with_nodes, :nodes_count => 3)
146
147       get api_way_path(way, :full => true)
148
149       assert_response :success
150
151       # Check the way is correctly returned
152       assert_select "osm way[id='#{way.id}'][version='1'][visible='true']", 1
153
154       # check that each node in the way appears once in the output as a
155       # reference and as the node element.
156       way.nodes.each do |n|
157         assert_select "osm way nd[ref='#{n.id}']", 1
158         assert_select "osm node[id='#{n.id}'][version='1'][lat='#{format('%<lat>.7f', :lat => n.lat)}'][lon='#{format('%<lon>.7f', :lon => n.lon)}']", 1
159       end
160     end
161
162     def test_show_full_json
163       way = create(:way_with_nodes, :nodes_count => 3)
164
165       get api_way_path(way, :full => true, :format => "json")
166
167       assert_response :success
168
169       # Check the way is correctly returned
170       js = ActiveSupport::JSON.decode(@response.body)
171       assert_not_nil js
172       assert_equal 4, js["elements"].count
173       js_ways = js["elements"].filter { |e| e["type"] == "way" }
174       assert_equal 1, js_ways.count
175       assert_equal way.id, js_ways[0]["id"]
176       assert_equal 1, js_ways[0]["version"]
177
178       # check that each node in the way appears once in the output as a
179       # reference and as the node element.
180       js_nodes = js["elements"].filter { |e| e["type"] == "node" }
181       assert_equal 3, js_nodes.count
182
183       way.nodes.each_with_index do |n, i|
184         assert_equal n.id, js_ways[0]["nodes"][i]
185         js_nodes_with_id = js_nodes.filter { |e| e["id"] == n.id }
186         assert_equal 1, js_nodes_with_id.count
187         assert_equal n.id, js_nodes_with_id[0]["id"]
188         assert_equal 1, js_nodes_with_id[0]["version"]
189         assert_equal n.lat, js_nodes_with_id[0]["lat"]
190         assert_equal n.lon, js_nodes_with_id[0]["lon"]
191       end
192     end
193
194     def test_show_full_deleted
195       way = create(:way, :deleted)
196
197       get api_way_path(way, :full => true)
198
199       assert_response :gone
200     end
201
202     # -------------------------------------
203     # Test creating ways.
204     # -------------------------------------
205
206     def test_create_by_private_user
207       node1 = create(:node)
208       node2 = create(:node)
209
210       with_unchanging_request([:data_public => false]) do |headers, changeset|
211         osm = <<~OSM
212           <osm>
213             <way changeset='#{changeset.id}'>
214               <nd ref='#{node1.id}'/>
215               <nd ref='#{node2.id}'/>
216               <tag k='test' v='yes' />
217             </way>
218           </osm>
219         OSM
220
221         post api_ways_path, :params => osm, :headers => headers
222
223         assert_response :forbidden, "way upload did not return forbidden status"
224       end
225     end
226
227     def test_create
228       node1 = create(:node)
229       node2 = create(:node)
230
231       with_request do |headers, changeset|
232         assert_difference "Way.count" => 1,
233                           "WayNode.count" => 2 do
234           osm = <<~OSM
235             <osm>
236               <way changeset='#{changeset.id}'>
237                 <nd ref='#{node1.id}'/>
238                 <nd ref='#{node2.id}'/>
239                 <tag k='test' v='yes' />
240               </way>
241             </osm>
242           OSM
243
244           post api_ways_path, :params => osm, :headers => headers
245
246           assert_response :success, "way upload did not return success status"
247         end
248
249         created_way_id = @response.body
250         way = Way.find(created_way_id)
251         assert_equal [node1, node2], way.nodes
252         assert_equal changeset.id, way.changeset_id, "saved way does not belong to the correct changeset"
253         assert way.visible, "saved way is not visible"
254
255         changeset.reload
256         assert_equal 1, changeset.num_changes
257         assert_predicate changeset, :num_type_changes_in_sync?
258         assert_equal 1, changeset.num_created_ways
259       end
260     end
261
262     def test_create_in_missing_changeset
263       node1 = create(:node)
264       node2 = create(:node)
265
266       with_unchanging_request do |headers|
267         osm = <<~OSM
268           <osm>
269             <way changeset='0'>
270               <nd ref='#{node1.id}'/>
271               <nd ref='#{node2.id}'/>
272             </way>
273           </osm>
274         OSM
275
276         post api_ways_path, :params => osm, :headers => headers
277
278         assert_response :conflict
279       end
280     end
281
282     def test_create_with_missing_node_by_private_user
283       with_unchanging_request([:data_public => false]) do |headers, changeset|
284         osm = <<~OSM
285           <osm>
286             <way changeset='#{changeset.id}'>
287               <nd ref='0'/>
288             </way>
289           </osm>
290         OSM
291
292         post api_ways_path, :params => osm, :headers => headers
293
294         assert_response :forbidden, "way upload with invalid node using a private user did not return 'forbidden'"
295       end
296     end
297
298     def test_create_without_nodes_by_private_user
299       with_unchanging_request([:data_public => false]) do |headers, changeset|
300         osm = <<~OSM
301           <osm>
302             <way changeset='#{changeset.id}' />
303           </osm>
304         OSM
305
306         post api_ways_path, :params => osm, :headers => headers
307
308         assert_response :forbidden, "way upload with no node using a private user did not return 'forbidden'"
309       end
310     end
311
312     def test_create_in_closed_changeset_by_private_user
313       node = create(:node)
314
315       with_unchanging_request([:data_public => false]) do |headers, changeset|
316         osm = <<~OSM
317           <osm>
318             <way changeset='#{changeset.id}'>
319               <nd ref='#{node.id}'/>
320             </way>
321           </osm>
322         OSM
323
324         post api_ways_path, :params => osm, :headers => headers
325
326         assert_response :forbidden, "way upload to closed changeset with a private user did not return 'forbidden'"
327       end
328     end
329
330     def test_create_with_missing_node
331       with_unchanging_request do |headers, changeset|
332         osm = <<~OSM
333           <osm>
334             <way changeset='#{changeset.id}'>
335               <nd ref='0'/>
336             </way>
337           </osm>
338         OSM
339
340         post api_ways_path, :params => osm, :headers => headers
341
342         assert_response :precondition_failed, "way upload with invalid node did not return 'precondition failed'"
343         assert_equal "Precondition failed: Way  requires the nodes with id in (0), which either do not exist, or are not visible.", @response.body
344       end
345     end
346
347     def test_create_without_nodes
348       with_unchanging_request do |headers, changeset|
349         osm = <<~OSM
350           <osm>
351             <way changeset='#{changeset.id}' />
352           </osm>
353         OSM
354
355         post api_ways_path, :params => osm, :headers => headers
356
357         assert_response :precondition_failed, "way upload with no node did not return 'precondition failed'"
358         assert_equal "Precondition failed: Cannot create way: data is invalid.", @response.body
359       end
360     end
361
362     def test_create_in_closed_changeset
363       node = create(:node)
364
365       with_unchanging_request([], [:closed]) do |headers, changeset|
366         osm = <<~OSM
367           <osm>
368             <way changeset='#{changeset.id}'>
369               <nd ref='#{node.id}'/>
370             </way>
371           </osm>
372         OSM
373
374         post api_ways_path, :params => osm, :headers => headers
375
376         assert_response :conflict, "way upload to closed changeset did not return 'conflict'"
377       end
378     end
379
380     def test_create_with_tag_too_long
381       node = create(:node)
382
383       with_unchanging_request do |headers, changeset|
384         osm = <<~OSM
385           <osm>
386             <way changeset='#{changeset.id}'>
387               <nd ref='#{node.id}'/>
388               <tag k='foo' v='#{'x' * 256}'/>
389             </way>
390           </osm>
391         OSM
392
393         post api_ways_path, :params => osm, :headers => headers
394
395         assert_response :bad_request, "way upload to with too long tag did not return 'bad_request'"
396       end
397     end
398
399     def test_create_with_duplicate_tags_by_private_user
400       node = create(:node)
401
402       with_unchanging_request([:data_public => false]) do |headers, changeset|
403         osm = <<~OSM
404           <osm>
405             <way changeset='#{changeset.id}'>
406               <nd ref='#{node.id}'/>
407               <tag k='addr:housenumber' v='1'/>
408               <tag k='addr:housenumber' v='2'/>
409             </way>
410           </osm>
411         OSM
412
413         post api_ways_path, :params => osm, :headers => headers
414
415         assert_response :forbidden, "adding new duplicate tags to a way with a non-public user should fail with 'forbidden'"
416       end
417     end
418
419     def test_create_with_duplicate_tags
420       node = create(:node)
421
422       with_unchanging_request do |headers, changeset|
423         osm = <<~OSM
424           <osm>
425             <way changeset='#{changeset.id}'>
426               <nd ref='#{node.id}'/>
427               <tag k='addr:housenumber' v='1'/>
428               <tag k='addr:housenumber' v='2'/>
429             </way>
430           </osm>
431         OSM
432
433         post api_ways_path, :params => osm, :headers => headers
434
435         assert_response :bad_request, "adding new duplicate tags to a way should fail with 'bad request'"
436         assert_equal "Element way/ has duplicate tags with key addr:housenumber", @response.body
437       end
438     end
439
440     def test_create_race_condition
441       user = create(:user)
442       changeset = create(:changeset, :user => user)
443       node = create(:node)
444       auth_header = bearer_authorization_header user
445       path = api_ways_path
446       concurrency_level = 16
447
448       threads = Array.new(concurrency_level) do
449         Thread.new do
450           osm = <<~OSM
451             <osm>
452               <way changeset='#{changeset.id}'>
453                 <nd ref='#{node.id}'/>
454               </way>
455             </osm>
456           OSM
457           post path, :params => osm, :headers => auth_header
458         end
459       end
460       threads.each(&:join)
461
462       changeset.reload
463       assert_equal concurrency_level, changeset.num_changes
464       assert_predicate changeset, :num_type_changes_in_sync?
465       assert_equal concurrency_level, changeset.num_created_ways
466     end
467
468     # -------------------------------------
469     # Test deleting ways.
470     # -------------------------------------
471
472     def test_destroy_when_unauthorized
473       with_unchanging(:way) do |way|
474         delete api_way_path(way)
475
476         assert_response :unauthorized
477       end
478     end
479
480     def test_destroy_without_payload_by_private_user
481       with_unchanging(:way) do |way|
482         with_unchanging_request([:data_public => false]) do |headers|
483           delete api_way_path(way), :headers => headers
484
485           assert_response :forbidden
486         end
487       end
488     end
489
490     def test_destroy_without_changeset_id_by_private_user
491       with_unchanging(:way) do |way|
492         with_unchanging_request([:data_public => false]) do |headers|
493           osm = "<osm><way id='#{way.id}'/></osm>"
494
495           delete api_way_path(way), :params => osm, :headers => headers
496
497           assert_response :forbidden
498         end
499       end
500     end
501
502     def test_destroy_in_closed_changeset_by_private_user
503       with_unchanging(:way) do |way|
504         with_unchanging_request([:data_public => false], [:closed]) do |headers, changeset|
505           osm_xml = xml_for_way way
506           osm_xml = update_changeset osm_xml, changeset.id
507
508           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
509
510           assert_response :forbidden
511         end
512       end
513     end
514
515     def test_destroy_in_missing_changeset_by_private_user
516       with_unchanging(:way) do |way|
517         with_unchanging_request([:data_public => false]) do |headers|
518           osm_xml = xml_for_way way
519           osm_xml = update_changeset osm_xml, 0
520
521           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
522
523           assert_response :forbidden
524         end
525       end
526     end
527
528     def test_destroy_by_private_user
529       with_unchanging(:way) do |way|
530         with_unchanging_request([:data_public => false]) do |headers, changeset|
531           osm_xml = xml_for_way way
532           osm_xml = update_changeset osm_xml, changeset.id
533
534           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
535
536           assert_response :forbidden
537         end
538       end
539     end
540
541     def test_destroy_deleted_way_by_private_user
542       with_unchanging(:way, :deleted) do |way|
543         with_unchanging_request([:data_public => false]) do |headers, changeset|
544           osm_xml = xml_for_way way
545           osm_xml = update_changeset osm_xml, changeset.id
546
547           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
548
549           assert_response :forbidden
550         end
551       end
552     end
553
554     def test_destroy_way_in_relation_by_private_user
555       with_unchanging(:way) do |way|
556         create(:relation_member, :member => way)
557
558         with_unchanging_request([:data_public => false]) do |headers, changeset|
559           osm_xml = xml_for_way way
560           osm_xml = update_changeset osm_xml, changeset.id
561
562           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
563
564           assert_response :forbidden, "shouldn't be able to delete a way used in a relation (#{@response.body}), when done by a private user"
565         end
566       end
567     end
568
569     def test_destroy_missing_way_by_private_user
570       with_unchanging_request([:data_public => false]) do |headers|
571         delete api_way_path(0), :headers => headers
572
573         assert_response :forbidden
574       end
575     end
576
577     def test_destroy_without_payload
578       with_unchanging(:way) do |way|
579         with_unchanging_request do |headers|
580           delete api_way_path(way), :headers => headers
581
582           assert_response :bad_request
583         end
584       end
585     end
586
587     def test_destroy_without_changeset_id
588       with_unchanging(:way) do |way|
589         with_unchanging_request do |headers|
590           osm = "<osm><way id='#{way.id}'/></osm>"
591
592           delete api_way_path(way), :params => osm, :headers => headers
593
594           assert_response :bad_request
595         end
596       end
597     end
598
599     def test_destroy_in_closed_changeset
600       with_unchanging(:way) do |way|
601         with_unchanging_request([], [:closed]) do |headers, changeset|
602           osm_xml = xml_for_way way
603           osm_xml = update_changeset osm_xml, changeset.id
604
605           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
606
607           assert_response :conflict
608         end
609       end
610     end
611
612     def test_destroy_in_missing_changeset
613       with_unchanging(:way) do |way|
614         with_unchanging_request do |headers|
615           osm_xml = xml_for_way way
616           osm_xml = update_changeset osm_xml, 0
617
618           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
619
620           assert_response :conflict
621         end
622       end
623     end
624
625     def test_destroy
626       way = create(:way)
627
628       with_request do |headers, changeset|
629         osm_xml = xml_for_way way
630         osm_xml = update_changeset osm_xml, changeset.id
631
632         delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
633
634         assert_response :success
635
636         response_way_version = @response.body.to_i
637         assert_operator response_way_version, :>, way.version, "delete request should return a new version number for way"
638         way.reload
639         assert_not_predicate way, :visible?
640         assert_equal response_way_version, way.version
641
642         changeset.reload
643         assert_equal 1, changeset.num_changes
644         assert_predicate changeset, :num_type_changes_in_sync?
645         assert_equal 1, changeset.num_deleted_ways
646       end
647     end
648
649     def test_destroy_deleted_way
650       with_unchanging(:way, :deleted) do |way|
651         with_unchanging_request do |headers, changeset|
652           osm_xml = xml_for_way way
653           osm_xml = update_changeset osm_xml, changeset.id
654
655           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
656
657           assert_response :gone
658         end
659       end
660     end
661
662     def test_destroy_way_in_relation
663       with_unchanging(:way) do |way|
664         relation_member = create(:relation_member, :member => way)
665
666         with_unchanging_request do |headers, changeset|
667           osm_xml = xml_for_way way
668           osm_xml = update_changeset osm_xml, changeset.id
669
670           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
671
672           assert_response :precondition_failed, "shouldn't be able to delete a way used in a relation (#{@response.body})"
673           assert_equal "Precondition failed: Way #{way.id} is still used by relations #{relation_member.relation.id}.", @response.body
674         end
675       end
676     end
677
678     def test_destroy_missing_way_with_payload
679       with_unchanging(:way) do |way|
680         with_unchanging_request do |headers, changeset|
681           osm_xml = xml_for_way way
682           osm_xml = update_changeset osm_xml, changeset.id
683
684           delete api_way_path(0), :params => osm_xml.to_s, :headers => headers
685
686           assert_response :not_found
687         end
688       end
689     end
690
691     # -------------------------------------
692     # Test updating ways.
693     # -------------------------------------
694
695     def test_update_when_unauthorized
696       with_unchanging(:way_with_nodes) do |way|
697         osm_xml = xml_for_way way
698
699         put api_way_path(way), :params => osm_xml.to_s
700
701         assert_response :unauthorized
702       end
703     end
704
705     def test_update_in_changeset_of_other_user_by_private_user
706       with_unchanging(:way_with_nodes) do |way|
707         other_user = create(:user)
708
709         with_unchanging_request([:data_public => false], [:user => other_user]) do |headers, changeset|
710           osm_xml = xml_for_way way
711           osm_xml = update_changeset osm_xml, changeset.id
712
713           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
714
715           assert_require_public_data "update with other user's changeset should be forbidden when date isn't public"
716         end
717       end
718     end
719
720     def test_update_in_closed_changeset_by_private_user
721       with_unchanging(:way_with_nodes) do |way|
722         with_unchanging_request([:data_public => false], [:closed]) do |headers, changeset|
723           osm_xml = xml_for_way way
724           osm_xml = update_changeset osm_xml, changeset.id
725
726           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
727
728           assert_require_public_data "update with closed changeset should be forbidden, when data isn't public"
729         end
730       end
731     end
732
733     def test_update_in_missing_changeset_by_private_user
734       with_unchanging(:way_with_nodes) do |way|
735         with_unchanging_request([:data_public => false]) do |headers|
736           osm_xml = xml_for_way way
737           osm_xml = update_changeset osm_xml, 0
738
739           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
740
741           assert_require_public_data "update with changeset=0 should be forbidden, when data isn't public"
742         end
743       end
744     end
745
746     def test_update_with_missing_node_by_private_user
747       with_unchanging(:way) do |way|
748         node = create(:node)
749         create(:way_node, :way => way, :node => node)
750
751         with_unchanging_request([:data_public => false]) do |headers, changeset|
752           osm_xml = xml_for_way way
753           osm_xml = xml_replace_node osm_xml, node.id, 9999
754           osm_xml = update_changeset osm_xml, changeset.id
755
756           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
757
758           assert_require_public_data "way with non-existent node should be forbidden, when data isn't public"
759         end
760       end
761     end
762
763     def test_update_with_deleted_node_by_private_user
764       with_unchanging(:way) do |way|
765         node = create(:node)
766         deleted_node = create(:node, :deleted)
767         create(:way_node, :way => way, :node => node)
768
769         with_unchanging_request([:data_public => false]) do |headers, changeset|
770           osm_xml = xml_for_way way
771           osm_xml = xml_replace_node osm_xml, node.id, deleted_node.id
772           osm_xml = update_changeset osm_xml, changeset.id
773
774           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
775
776           assert_require_public_data "way with deleted node should be forbidden, when data isn't public"
777         end
778       end
779     end
780
781     def test_update_by_private_user
782       with_unchanging(:way_with_nodes) do |way|
783         with_unchanging_request([:data_public => false]) do |headers, changeset|
784           osm_xml = xml_for_way way
785           osm_xml = update_changeset osm_xml, changeset.id
786
787           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
788
789           assert_require_public_data "should have failed with a forbidden when data isn't public"
790         end
791       end
792     end
793
794     def test_update_in_changeset_of_other_user
795       with_unchanging(:way_with_nodes) do |way|
796         other_user = create(:user)
797
798         with_unchanging_request([], [:user => other_user]) do |headers, changeset|
799           osm_xml = xml_for_way way
800           osm_xml = update_changeset osm_xml, changeset.id
801
802           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
803
804           assert_response :conflict, "update with other user's changeset should be rejected"
805         end
806       end
807     end
808
809     def test_update_in_closed_changeset
810       with_unchanging(:way_with_nodes) do |way|
811         with_unchanging_request([], [:closed]) do |headers, changeset|
812           osm_xml = xml_for_way way
813           osm_xml = update_changeset osm_xml, changeset.id
814
815           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
816
817           assert_response :conflict, "update with closed changeset should be rejected"
818         end
819       end
820     end
821
822     def test_update_in_missing_changeset
823       with_unchanging(:way_with_nodes) do |way|
824         with_unchanging_request do |headers|
825           osm_xml = xml_for_way way
826           osm_xml = update_changeset osm_xml, 0
827
828           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
829
830           assert_response :conflict, "update with changeset=0 should be rejected"
831         end
832       end
833     end
834
835     def test_update_with_missing_node
836       with_unchanging(:way) do |way|
837         node = create(:node)
838         create(:way_node, :way => way, :node => node)
839
840         with_unchanging_request do |headers, changeset|
841           osm_xml = xml_for_way way
842           osm_xml = xml_replace_node osm_xml, node.id, 9999
843           osm_xml = update_changeset osm_xml, changeset.id
844
845           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
846
847           assert_response :precondition_failed, "way with non-existent node should be rejected"
848         end
849       end
850     end
851
852     def test_update_with_deleted_node
853       with_unchanging(:way) do |way|
854         node = create(:node)
855         deleted_node = create(:node, :deleted)
856         create(:way_node, :way => way, :node => node)
857
858         with_unchanging_request do |headers, changeset|
859           osm_xml = xml_for_way way
860           osm_xml = xml_replace_node osm_xml, node.id, deleted_node.id
861           osm_xml = update_changeset osm_xml, changeset.id
862
863           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
864
865           assert_response :precondition_failed, "way with deleted node should be rejected"
866         end
867       end
868     end
869
870     def test_update_with_version_behind
871       with_unchanging(:way_with_nodes, :version => 2) do |way|
872         with_unchanging_request do |headers, changeset|
873           osm_xml = xml_for_way way
874           osm_xml = xml_attr_rewrite osm_xml, "version", way.version - 1
875           osm_xml = update_changeset osm_xml, changeset.id
876
877           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
878
879           assert_response :conflict, "should have failed on old version number"
880         end
881       end
882     end
883
884     def test_update_with_version_ahead
885       with_unchanging(:way_with_nodes, :version => 2) do |way|
886         with_unchanging_request do |headers, changeset|
887           osm_xml = xml_for_way way
888           osm_xml = xml_attr_rewrite osm_xml, "version", way.version + 1
889           osm_xml = update_changeset osm_xml, changeset.id
890
891           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
892
893           assert_response :conflict, "should have failed on skipped version number"
894         end
895       end
896     end
897
898     def test_update_with_invalid_version
899       with_unchanging(:way_with_nodes) do |way|
900         with_unchanging_request do |headers, changeset|
901           osm_xml = xml_for_way way
902           osm_xml = xml_attr_rewrite osm_xml, "version", "p1r4t3s!"
903           osm_xml = update_changeset osm_xml, changeset.id
904
905           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
906
907           assert_response :conflict, "should not be able to put 'p1r4at3s!' in the version field"
908         end
909       end
910     end
911
912     def test_update_other_way
913       with_unchanging(:way_with_nodes) do |way|
914         with_unchanging(:way_with_nodes) do |other_way|
915           with_unchanging_request do |headers, changeset|
916             osm_xml = xml_for_way other_way
917             osm_xml = update_changeset osm_xml, changeset.id
918
919             put api_way_path(way), :params => osm_xml.to_s, :headers => headers
920
921             assert_response :bad_request, "should not be able to update a way with a different ID from the XML"
922           end
923         end
924       end
925     end
926
927     def test_update_with_invalid_osm_structure
928       with_unchanging(:way_with_nodes) do |way|
929         with_unchanging_request do |headers|
930           osm = "<update/>"
931
932           put api_way_path(way), :params => osm, :headers => headers
933
934           assert_response :bad_request, "should not be able to update a way with non-OSM XML doc."
935         end
936       end
937     end
938
939     def test_update
940       way = create(:way_with_nodes)
941
942       with_request do |headers, changeset|
943         osm_xml = xml_for_way way
944         osm_xml = update_changeset osm_xml, changeset.id
945
946         put api_way_path(way), :params => osm_xml.to_s, :headers => headers
947
948         assert_response :success, "a valid update request failed"
949
950         changeset.reload
951         assert_equal 1, changeset.num_changes
952         assert_predicate changeset, :num_type_changes_in_sync?
953         assert_equal 1, changeset.num_modified_ways
954       end
955     end
956
957     def test_update_with_new_tags_by_private_user
958       with_unchanging(:way_with_nodes, :nodes_count => 2) do |way|
959         with_unchanging_request([:data_public => false]) do |headers, changeset|
960           tag_xml = XML::Node.new("tag")
961           tag_xml["k"] = "new"
962           tag_xml["v"] = "yes"
963
964           osm_xml = xml_for_way way
965           osm_xml.find("//osm/way").first << tag_xml
966           osm_xml = update_changeset osm_xml, changeset.id
967
968           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
969
970           assert_response :forbidden, "adding a tag to a way for a non-public should fail with 'forbidden'"
971         end
972       end
973     end
974
975     def test_update_with_new_tags
976       way = create(:way_with_nodes, :nodes_count => 2)
977
978       with_request do |headers, changeset|
979         tag_xml = XML::Node.new("tag")
980         tag_xml["k"] = "new"
981         tag_xml["v"] = "yes"
982
983         osm_xml = xml_for_way way
984         osm_xml.find("//osm/way").first << tag_xml
985         osm_xml = update_changeset osm_xml, changeset.id
986
987         put api_way_path(way), :params => osm_xml.to_s, :headers => headers
988
989         assert_response :success, "adding a new tag to a way should succeed"
990         assert_equal way.version + 1, @response.body.to_i
991
992         changeset.reload
993         assert_equal 1, changeset.num_changes
994         assert_predicate changeset, :num_type_changes_in_sync?
995         assert_equal 1, changeset.num_modified_ways
996       end
997     end
998
999     def test_update_with_duplicated_existing_tags_by_private_user
1000       with_unchanging(:way_with_nodes) do |way|
1001         create(:way_tag, :way => way, :k => "key_to_duplicate", :v => "value_to_duplicate")
1002
1003         with_unchanging_request([:data_public => false]) do |headers, changeset|
1004           tag_xml = XML::Node.new("tag")
1005           tag_xml["k"] = "key_to_duplicate"
1006           tag_xml["v"] = "value_to_duplicate"
1007
1008           osm_xml = xml_for_way way
1009           osm_xml.find("//osm/way").first << tag_xml
1010           osm_xml = update_changeset osm_xml, changeset.id
1011
1012           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
1013
1014           assert_response :forbidden, "adding a duplicate tag to a way for a non-public should fail with 'forbidden'"
1015         end
1016       end
1017     end
1018
1019     def test_update_with_duplicated_existing_tags
1020       with_unchanging(:way_with_nodes) do |way|
1021         create(:way_tag, :way => way, :k => "key_to_duplicate", :v => "value_to_duplicate")
1022
1023         with_unchanging_request do |headers, changeset|
1024           tag_xml = XML::Node.new("tag")
1025           tag_xml["k"] = "key_to_duplicate"
1026           tag_xml["v"] = "value_to_duplicate"
1027
1028           osm_xml = xml_for_way way
1029           osm_xml.find("//osm/way").first << tag_xml
1030           osm_xml = update_changeset osm_xml, changeset.id
1031
1032           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
1033
1034           assert_response :bad_request, "adding a duplicate tag to a way should fail with 'bad request'"
1035           assert_equal "Element way/#{way.id} has duplicate tags with key key_to_duplicate", @response.body
1036         end
1037       end
1038     end
1039
1040     def test_update_with_new_duplicate_tags_by_private_user
1041       with_unchanging(:way_with_nodes) do |way|
1042         with_unchanging_request([:data_public => false]) do |headers, changeset|
1043           tag_xml = XML::Node.new("tag")
1044           tag_xml["k"] = "i_am_a_duplicate"
1045           tag_xml["v"] = "foobar"
1046
1047           osm_xml = xml_for_way way
1048           osm_xml.find("//osm/way").first << tag_xml.copy(true) << tag_xml
1049           osm_xml = update_changeset osm_xml, changeset.id
1050
1051           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
1052
1053           assert_response :forbidden, "adding new duplicate tags to a way using a non-public user should fail with 'forbidden'"
1054         end
1055       end
1056     end
1057
1058     def test_update_with_new_duplicate_tags
1059       with_unchanging(:way_with_nodes) do |way|
1060         with_unchanging_request do |headers, changeset|
1061           tag_xml = XML::Node.new("tag")
1062           tag_xml["k"] = "i_am_a_duplicate"
1063           tag_xml["v"] = "foobar"
1064
1065           osm_xml = xml_for_way way
1066           osm_xml.find("//osm/way").first << tag_xml.copy(true) << tag_xml
1067           osm_xml = update_changeset osm_xml, changeset.id
1068
1069           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
1070
1071           assert_response :bad_request, "adding new duplicate tags to a way should fail with 'bad request'"
1072           assert_equal "Element way/#{way.id} has duplicate tags with key i_am_a_duplicate", @response.body
1073         end
1074       end
1075     end
1076
1077     ##
1078     # test initial rate limit
1079     def test_initial_rate_limit
1080       # create a user
1081       user = create(:user)
1082
1083       # create some nodes
1084       node1 = create(:node)
1085       node2 = create(:node)
1086
1087       # create a changeset that puts us near the initial rate limit
1088       changeset = create(:changeset, :user => user,
1089                                      :created_at => Time.now.utc - 5.minutes,
1090                                      :num_changes => Settings.initial_changes_per_hour - 1)
1091
1092       # create authentication header
1093       auth_header = bearer_authorization_header user
1094
1095       # try creating a way
1096       xml = <<~OSM
1097         <osm>
1098           <way changeset='#{changeset.id}'>
1099             <nd ref='#{node1.id}'/>
1100             <nd ref='#{node2.id}'/>
1101           </way>
1102         </osm>
1103       OSM
1104       post api_ways_path, :params => xml, :headers => auth_header
1105       assert_response :success, "way create did not return success status"
1106
1107       # get the id of the way we created
1108       wayid = @response.body
1109
1110       # try updating the way, which should be rate limited
1111       xml = <<~OSM
1112         <osm>
1113           <way id='#{wayid}' version='1' changeset='#{changeset.id}'>
1114             <nd ref='#{node2.id}'/>
1115             <nd ref='#{node1.id}'/>
1116           </way>
1117         </osm>
1118       OSM
1119       put api_way_path(wayid), :params => xml, :headers => auth_header
1120       assert_response :too_many_requests, "way update did not hit rate limit"
1121
1122       # try deleting the way, which should be rate limited
1123       xml = "<osm><way id='#{wayid}' version='2' changeset='#{changeset.id}'/></osm>"
1124       delete api_way_path(wayid), :params => xml, :headers => auth_header
1125       assert_response :too_many_requests, "way delete did not hit rate limit"
1126
1127       # try creating a way, which should be rate limited
1128       xml = <<~OSM
1129         <osm>
1130           <way changeset='#{changeset.id}'>
1131             <nd ref='#{node1.id}'/>
1132             <nd ref='#{node2.id}'/>
1133           </way>
1134         </osm>
1135       OSM
1136       post api_ways_path, :params => xml, :headers => auth_header
1137       assert_response :too_many_requests, "way create did not hit rate limit"
1138     end
1139
1140     ##
1141     # test maximum rate limit
1142     def test_maximum_rate_limit
1143       # create a user
1144       user = create(:user)
1145
1146       # create some nodes
1147       node1 = create(:node)
1148       node2 = create(:node)
1149
1150       # create a changeset to establish our initial edit time
1151       changeset = create(:changeset, :user => user,
1152                                      :created_at => Time.now.utc - 28.days)
1153
1154       # create changeset to put us near the maximum rate limit
1155       total_changes = Settings.max_changes_per_hour - 1
1156       while total_changes.positive?
1157         changes = [total_changes, Changeset::MAX_ELEMENTS].min
1158         changeset = create(:changeset, :user => user,
1159                                        :created_at => Time.now.utc - 5.minutes,
1160                                        :num_changes => changes)
1161         total_changes -= changes
1162       end
1163
1164       # create authentication header
1165       auth_header = bearer_authorization_header user
1166
1167       # try creating a way
1168       xml = <<~OSM
1169         <osm>
1170           <way changeset='#{changeset.id}'>
1171             <nd ref='#{node1.id}'/>
1172             <nd ref='#{node2.id}'/>
1173           </way>
1174         </osm>
1175       OSM
1176       post api_ways_path, :params => xml, :headers => auth_header
1177       assert_response :success, "way create did not return success status"
1178
1179       # get the id of the way we created
1180       wayid = @response.body
1181
1182       # try updating the way, which should be rate limited
1183       xml = <<~OSM
1184         <osm>
1185           <way id='#{wayid}' version='1' changeset='#{changeset.id}'>
1186             <nd ref='#{node2.id}'/>
1187             <nd ref='#{node1.id}'/>
1188           </way>
1189         </osm>
1190       OSM
1191       put api_way_path(wayid), :params => xml, :headers => auth_header
1192       assert_response :too_many_requests, "way update did not hit rate limit"
1193
1194       # try deleting the way, which should be rate limited
1195       xml = "<osm><way id='#{wayid}' version='2' changeset='#{changeset.id}'/></osm>"
1196       delete api_way_path(wayid), :params => xml, :headers => auth_header
1197       assert_response :too_many_requests, "way delete did not hit rate limit"
1198
1199       # try creating a way, which should be rate limited
1200       xml = <<~OSM
1201         <osm>
1202           <way changeset='#{changeset.id}'>
1203             <nd ref='#{node1.id}'/>
1204             <nd ref='#{node2.id}'/>
1205           </way>
1206         </osm>
1207       OSM
1208       post api_ways_path, :params => xml, :headers => auth_header
1209       assert_response :too_many_requests, "way create did not hit rate limit"
1210     end
1211
1212     private
1213
1214     def affected_models
1215       [Way, WayNode, WayTag,
1216        OldWay, OldWayNode, OldWayTag]
1217     end
1218
1219     ##
1220     # update an attribute in the way element
1221     def xml_attr_rewrite(xml, name, value)
1222       xml.find("//osm/way").first[name] = value.to_s
1223       xml
1224     end
1225
1226     ##
1227     # replace a node in a way element
1228     def xml_replace_node(xml, old_node, new_node)
1229       xml.find("//osm/way/nd[@ref='#{old_node}']").first["ref"] = new_node.to_s
1230       xml
1231     end
1232   end
1233 end