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