]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/ways_controller_test.rb
Add missing changeset tests for api element writes
[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     # -------------------------------------
439     # Test deleting ways.
440     # -------------------------------------
441
442     def test_destroy_when_unauthorized
443       with_unchanging(:way) do |way|
444         delete api_way_path(way)
445
446         assert_response :unauthorized
447       end
448     end
449
450     def test_destroy_without_payload_by_private_user
451       with_unchanging(:way) do |way|
452         with_unchanging_request([:data_public => false]) do |headers|
453           delete api_way_path(way), :headers => headers
454
455           assert_response :forbidden
456         end
457       end
458     end
459
460     def test_destroy_without_changeset_id_by_private_user
461       with_unchanging(:way) do |way|
462         with_unchanging_request([:data_public => false]) do |headers|
463           osm = "<osm><way id='#{way.id}'/></osm>"
464
465           delete api_way_path(way), :params => osm, :headers => headers
466
467           assert_response :forbidden
468         end
469       end
470     end
471
472     def test_destroy_in_closed_changeset_by_private_user
473       with_unchanging(:way) do |way|
474         with_unchanging_request([:data_public => false], [:closed]) do |headers, changeset|
475           osm_xml = xml_for_way way
476           osm_xml = update_changeset osm_xml, changeset.id
477
478           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
479
480           assert_response :forbidden
481         end
482       end
483     end
484
485     def test_destroy_in_missing_changeset_by_private_user
486       with_unchanging(:way) do |way|
487         with_unchanging_request([:data_public => false]) do |headers|
488           osm_xml = xml_for_way way
489           osm_xml = update_changeset osm_xml, 0
490
491           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
492
493           assert_response :forbidden
494         end
495       end
496     end
497
498     def test_destroy_by_private_user
499       with_unchanging(:way) do |way|
500         with_unchanging_request([:data_public => false]) do |headers, changeset|
501           osm_xml = xml_for_way way
502           osm_xml = update_changeset osm_xml, changeset.id
503
504           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
505
506           assert_response :forbidden
507         end
508       end
509     end
510
511     def test_destroy_deleted_way_by_private_user
512       with_unchanging(:way, :deleted) do |way|
513         with_unchanging_request([:data_public => false]) do |headers, changeset|
514           osm_xml = xml_for_way way
515           osm_xml = update_changeset osm_xml, changeset.id
516
517           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
518
519           assert_response :forbidden
520         end
521       end
522     end
523
524     def test_destroy_way_in_relation_by_private_user
525       with_unchanging(:way) do |way|
526         create(:relation_member, :member => way)
527
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, "shouldn't be able to delete a way used in a relation (#{@response.body}), when done by a private user"
535         end
536       end
537     end
538
539     def test_destroy_missing_way_by_private_user
540       with_unchanging_request([:data_public => false]) do |headers|
541         delete api_way_path(0), :headers => headers
542
543         assert_response :forbidden
544       end
545     end
546
547     def test_destroy_without_payload
548       with_unchanging(:way) do |way|
549         with_unchanging_request do |headers|
550           delete api_way_path(way), :headers => headers
551
552           assert_response :bad_request
553         end
554       end
555     end
556
557     def test_destroy_without_changeset_id
558       with_unchanging(:way) do |way|
559         with_unchanging_request do |headers|
560           osm = "<osm><way id='#{way.id}'/></osm>"
561
562           delete api_way_path(way), :params => osm, :headers => headers
563
564           assert_response :bad_request
565         end
566       end
567     end
568
569     def test_destroy_in_closed_changeset
570       with_unchanging(:way) do |way|
571         with_unchanging_request([], [:closed]) do |headers, changeset|
572           osm_xml = xml_for_way way
573           osm_xml = update_changeset osm_xml, changeset.id
574
575           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
576
577           assert_response :conflict
578         end
579       end
580     end
581
582     def test_destroy_in_missing_changeset
583       with_unchanging(:way) do |way|
584         with_unchanging_request do |headers|
585           osm_xml = xml_for_way way
586           osm_xml = update_changeset osm_xml, 0
587
588           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
589
590           assert_response :conflict
591         end
592       end
593     end
594
595     def test_destroy
596       way = create(:way)
597
598       with_request do |headers, changeset|
599         osm_xml = xml_for_way way
600         osm_xml = update_changeset osm_xml, changeset.id
601
602         delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
603
604         assert_response :success
605
606         response_way_version = @response.body.to_i
607         assert_operator response_way_version, :>, way.version, "delete request should return a new version number for way"
608         way.reload
609         assert_not_predicate way, :visible?
610         assert_equal response_way_version, way.version
611
612         changeset.reload
613         assert_equal 1, changeset.num_changes
614         assert_predicate changeset, :num_type_changes_in_sync?
615         assert_equal 1, changeset.num_deleted_ways
616       end
617     end
618
619     def test_destroy_deleted_way
620       with_unchanging(:way, :deleted) do |way|
621         with_unchanging_request do |headers, changeset|
622           osm_xml = xml_for_way way
623           osm_xml = update_changeset osm_xml, changeset.id
624
625           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
626
627           assert_response :gone
628         end
629       end
630     end
631
632     def test_destroy_way_in_relation
633       with_unchanging(:way) do |way|
634         relation_member = create(:relation_member, :member => way)
635
636         with_unchanging_request do |headers, changeset|
637           osm_xml = xml_for_way way
638           osm_xml = update_changeset osm_xml, changeset.id
639
640           delete api_way_path(way), :params => osm_xml.to_s, :headers => headers
641
642           assert_response :precondition_failed, "shouldn't be able to delete a way used in a relation (#{@response.body})"
643           assert_equal "Precondition failed: Way #{way.id} is still used by relations #{relation_member.relation.id}.", @response.body
644         end
645       end
646     end
647
648     def test_destroy_missing_way_with_payload
649       with_unchanging(:way) do |way|
650         with_unchanging_request do |headers, changeset|
651           osm_xml = xml_for_way way
652           osm_xml = update_changeset osm_xml, changeset.id
653
654           delete api_way_path(0), :params => osm_xml.to_s, :headers => headers
655
656           assert_response :not_found
657         end
658       end
659     end
660
661     # -------------------------------------
662     # Test updating ways.
663     # -------------------------------------
664
665     def test_update_when_unauthorized
666       with_unchanging(:way_with_nodes) do |way|
667         osm_xml = xml_for_way way
668
669         put api_way_path(way), :params => osm_xml.to_s
670
671         assert_response :unauthorized
672       end
673     end
674
675     def test_update_in_changeset_of_other_user_by_private_user
676       with_unchanging(:way_with_nodes) do |way|
677         other_user = create(:user)
678
679         with_unchanging_request([:data_public => false], [:user => other_user]) do |headers, changeset|
680           osm_xml = xml_for_way way
681           osm_xml = update_changeset osm_xml, changeset.id
682
683           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
684
685           assert_require_public_data "update with other user's changeset should be forbidden when date isn't public"
686         end
687       end
688     end
689
690     def test_update_in_closed_changeset_by_private_user
691       with_unchanging(:way_with_nodes) do |way|
692         with_unchanging_request([:data_public => false], [:closed]) do |headers, changeset|
693           osm_xml = xml_for_way way
694           osm_xml = update_changeset osm_xml, changeset.id
695
696           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
697
698           assert_require_public_data "update with closed changeset should be forbidden, when data isn't public"
699         end
700       end
701     end
702
703     def test_update_in_missing_changeset_by_private_user
704       with_unchanging(:way_with_nodes) do |way|
705         with_unchanging_request([:data_public => false]) do |headers|
706           osm_xml = xml_for_way way
707           osm_xml = update_changeset osm_xml, 0
708
709           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
710
711           assert_require_public_data "update with changeset=0 should be forbidden, when data isn't public"
712         end
713       end
714     end
715
716     def test_update_with_missing_node_by_private_user
717       with_unchanging(:way) do |way|
718         node = create(:node)
719         create(:way_node, :way => way, :node => node)
720
721         with_unchanging_request([:data_public => false]) do |headers, changeset|
722           osm_xml = xml_for_way way
723           osm_xml = xml_replace_node osm_xml, node.id, 9999
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 "way with non-existent node should be forbidden, when data isn't public"
729         end
730       end
731     end
732
733     def test_update_with_deleted_node_by_private_user
734       with_unchanging(:way) do |way|
735         node = create(:node)
736         deleted_node = create(:node, :deleted)
737         create(:way_node, :way => way, :node => node)
738
739         with_unchanging_request([:data_public => false]) do |headers, changeset|
740           osm_xml = xml_for_way way
741           osm_xml = xml_replace_node osm_xml, node.id, deleted_node.id
742           osm_xml = update_changeset osm_xml, changeset.id
743
744           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
745
746           assert_require_public_data "way with deleted node should be forbidden, when data isn't public"
747         end
748       end
749     end
750
751     def test_update_by_private_user
752       with_unchanging(:way_with_nodes) do |way|
753         with_unchanging_request([:data_public => false]) do |headers, changeset|
754           osm_xml = xml_for_way way
755           osm_xml = update_changeset osm_xml, changeset.id
756
757           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
758
759           assert_require_public_data "should have failed with a forbidden when data isn't public"
760         end
761       end
762     end
763
764     def test_update_in_changeset_of_other_user
765       with_unchanging(:way_with_nodes) do |way|
766         other_user = create(:user)
767
768         with_unchanging_request([], [:user => other_user]) do |headers, changeset|
769           osm_xml = xml_for_way way
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_response :conflict, "update with other user's changeset should be rejected"
775         end
776       end
777     end
778
779     def test_update_in_closed_changeset
780       with_unchanging(:way_with_nodes) do |way|
781         with_unchanging_request([], [:closed]) 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_response :conflict, "update with closed changeset should be rejected"
788         end
789       end
790     end
791
792     def test_update_in_missing_changeset
793       with_unchanging(:way_with_nodes) do |way|
794         with_unchanging_request do |headers|
795           osm_xml = xml_for_way way
796           osm_xml = update_changeset osm_xml, 0
797
798           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
799
800           assert_response :conflict, "update with changeset=0 should be rejected"
801         end
802       end
803     end
804
805     def test_update_with_missing_node
806       with_unchanging(:way) do |way|
807         node = create(:node)
808         create(:way_node, :way => way, :node => node)
809
810         with_unchanging_request do |headers, changeset|
811           osm_xml = xml_for_way way
812           osm_xml = xml_replace_node osm_xml, node.id, 9999
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 :precondition_failed, "way with non-existent node should be rejected"
818         end
819       end
820     end
821
822     def test_update_with_deleted_node
823       with_unchanging(:way) do |way|
824         node = create(:node)
825         deleted_node = create(:node, :deleted)
826         create(:way_node, :way => way, :node => node)
827
828         with_unchanging_request do |headers, changeset|
829           osm_xml = xml_for_way way
830           osm_xml = xml_replace_node osm_xml, node.id, deleted_node.id
831           osm_xml = update_changeset osm_xml, changeset.id
832
833           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
834
835           assert_response :precondition_failed, "way with deleted node should be rejected"
836         end
837       end
838     end
839
840     def test_update_with_version_behind
841       with_unchanging(:way_with_nodes, :version => 2) do |way|
842         with_unchanging_request do |headers, changeset|
843           osm_xml = xml_for_way way
844           osm_xml = xml_attr_rewrite osm_xml, "version", way.version - 1
845           osm_xml = update_changeset osm_xml, changeset.id
846
847           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
848
849           assert_response :conflict, "should have failed on old version number"
850         end
851       end
852     end
853
854     def test_update_with_version_ahead
855       with_unchanging(:way_with_nodes, :version => 2) do |way|
856         with_unchanging_request do |headers, changeset|
857           osm_xml = xml_for_way way
858           osm_xml = xml_attr_rewrite osm_xml, "version", way.version + 1
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 :conflict, "should have failed on skipped version number"
864         end
865       end
866     end
867
868     def test_update_with_invalid_version
869       with_unchanging(:way_with_nodes) 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", "p1r4t3s!"
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 not be able to put 'p1r4at3s!' in the version field"
878         end
879       end
880     end
881
882     def test_update_other_way
883       with_unchanging(:way_with_nodes) do |way|
884         with_unchanging(:way_with_nodes) do |other_way|
885           with_unchanging_request do |headers, changeset|
886             osm_xml = xml_for_way other_way
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 :bad_request, "should not be able to update a way with a different ID from the XML"
892           end
893         end
894       end
895     end
896
897     def test_update_with_invalid_osm_structure
898       with_unchanging(:way_with_nodes) do |way|
899         with_unchanging_request do |headers|
900           osm = "<update/>"
901
902           put api_way_path(way), :params => osm, :headers => headers
903
904           assert_response :bad_request, "should not be able to update a way with non-OSM XML doc."
905         end
906       end
907     end
908
909     def test_update
910       way = create(:way_with_nodes)
911
912       with_request do |headers, changeset|
913         osm_xml = xml_for_way way
914         osm_xml = update_changeset osm_xml, changeset.id
915
916         put api_way_path(way), :params => osm_xml.to_s, :headers => headers
917
918         assert_response :success, "a valid update request failed"
919
920         changeset.reload
921         assert_equal 1, changeset.num_changes
922         assert_predicate changeset, :num_type_changes_in_sync?
923         assert_equal 1, changeset.num_modified_ways
924       end
925     end
926
927     def test_update_with_new_tags_by_private_user
928       with_unchanging(:way_with_nodes, :nodes_count => 2) do |way|
929         with_unchanging_request([:data_public => false]) do |headers, changeset|
930           tag_xml = XML::Node.new("tag")
931           tag_xml["k"] = "new"
932           tag_xml["v"] = "yes"
933
934           osm_xml = xml_for_way way
935           osm_xml.find("//osm/way").first << tag_xml
936           osm_xml = update_changeset osm_xml, changeset.id
937
938           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
939
940           assert_response :forbidden, "adding a tag to a way for a non-public should fail with 'forbidden'"
941         end
942       end
943     end
944
945     def test_update_with_new_tags
946       way = create(:way_with_nodes, :nodes_count => 2)
947
948       with_request do |headers, changeset|
949         tag_xml = XML::Node.new("tag")
950         tag_xml["k"] = "new"
951         tag_xml["v"] = "yes"
952
953         osm_xml = xml_for_way way
954         osm_xml.find("//osm/way").first << tag_xml
955         osm_xml = update_changeset osm_xml, changeset.id
956
957         put api_way_path(way), :params => osm_xml.to_s, :headers => headers
958
959         assert_response :success, "adding a new tag to a way should succeed"
960         assert_equal way.version + 1, @response.body.to_i
961
962         changeset.reload
963         assert_equal 1, changeset.num_changes
964         assert_predicate changeset, :num_type_changes_in_sync?
965         assert_equal 1, changeset.num_modified_ways
966       end
967     end
968
969     def test_update_with_duplicated_existing_tags_by_private_user
970       with_unchanging(:way_with_nodes) do |way|
971         create(:way_tag, :way => way, :k => "key_to_duplicate", :v => "value_to_duplicate")
972
973         with_unchanging_request([:data_public => false]) do |headers, changeset|
974           tag_xml = XML::Node.new("tag")
975           tag_xml["k"] = "key_to_duplicate"
976           tag_xml["v"] = "value_to_duplicate"
977
978           osm_xml = xml_for_way way
979           osm_xml.find("//osm/way").first << tag_xml
980           osm_xml = update_changeset osm_xml, changeset.id
981
982           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
983
984           assert_response :forbidden, "adding a duplicate tag to a way for a non-public should fail with 'forbidden'"
985         end
986       end
987     end
988
989     def test_update_with_duplicated_existing_tags
990       with_unchanging(:way_with_nodes) do |way|
991         create(:way_tag, :way => way, :k => "key_to_duplicate", :v => "value_to_duplicate")
992
993         with_unchanging_request do |headers, changeset|
994           tag_xml = XML::Node.new("tag")
995           tag_xml["k"] = "key_to_duplicate"
996           tag_xml["v"] = "value_to_duplicate"
997
998           osm_xml = xml_for_way way
999           osm_xml.find("//osm/way").first << tag_xml
1000           osm_xml = update_changeset osm_xml, changeset.id
1001
1002           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
1003
1004           assert_response :bad_request, "adding a duplicate tag to a way should fail with 'bad request'"
1005           assert_equal "Element way/#{way.id} has duplicate tags with key key_to_duplicate", @response.body
1006         end
1007       end
1008     end
1009
1010     def test_update_with_new_duplicate_tags_by_private_user
1011       with_unchanging(:way_with_nodes) do |way|
1012         with_unchanging_request([:data_public => false]) do |headers, changeset|
1013           tag_xml = XML::Node.new("tag")
1014           tag_xml["k"] = "i_am_a_duplicate"
1015           tag_xml["v"] = "foobar"
1016
1017           osm_xml = xml_for_way way
1018           osm_xml.find("//osm/way").first << tag_xml.copy(true) << tag_xml
1019           osm_xml = update_changeset osm_xml, changeset.id
1020
1021           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
1022
1023           assert_response :forbidden, "adding new duplicate tags to a way using a non-public user should fail with 'forbidden'"
1024         end
1025       end
1026     end
1027
1028     def test_update_with_new_duplicate_tags
1029       with_unchanging(:way_with_nodes) do |way|
1030         with_unchanging_request do |headers, changeset|
1031           tag_xml = XML::Node.new("tag")
1032           tag_xml["k"] = "i_am_a_duplicate"
1033           tag_xml["v"] = "foobar"
1034
1035           osm_xml = xml_for_way way
1036           osm_xml.find("//osm/way").first << tag_xml.copy(true) << tag_xml
1037           osm_xml = update_changeset osm_xml, changeset.id
1038
1039           put api_way_path(way), :params => osm_xml.to_s, :headers => headers
1040
1041           assert_response :bad_request, "adding new duplicate tags to a way should fail with 'bad request'"
1042           assert_equal "Element way/#{way.id} has duplicate tags with key i_am_a_duplicate", @response.body
1043         end
1044       end
1045     end
1046
1047     ##
1048     # test initial rate limit
1049     def test_initial_rate_limit
1050       # create a user
1051       user = create(:user)
1052
1053       # create some nodes
1054       node1 = create(:node)
1055       node2 = create(:node)
1056
1057       # create a changeset that puts us near the initial rate limit
1058       changeset = create(:changeset, :user => user,
1059                                      :created_at => Time.now.utc - 5.minutes,
1060                                      :num_changes => Settings.initial_changes_per_hour - 1)
1061
1062       # create authentication header
1063       auth_header = bearer_authorization_header user
1064
1065       # try creating a way
1066       xml = <<~OSM
1067         <osm>
1068           <way changeset='#{changeset.id}'>
1069             <nd ref='#{node1.id}'/>
1070             <nd ref='#{node2.id}'/>
1071           </way>
1072         </osm>
1073       OSM
1074       post api_ways_path, :params => xml, :headers => auth_header
1075       assert_response :success, "way create did not return success status"
1076
1077       # get the id of the way we created
1078       wayid = @response.body
1079
1080       # try updating the way, which should be rate limited
1081       xml = <<~OSM
1082         <osm>
1083           <way id='#{wayid}' version='1' changeset='#{changeset.id}'>
1084             <nd ref='#{node2.id}'/>
1085             <nd ref='#{node1.id}'/>
1086           </way>
1087         </osm>
1088       OSM
1089       put api_way_path(wayid), :params => xml, :headers => auth_header
1090       assert_response :too_many_requests, "way update did not hit rate limit"
1091
1092       # try deleting the way, which should be rate limited
1093       xml = "<osm><way id='#{wayid}' version='2' changeset='#{changeset.id}'/></osm>"
1094       delete api_way_path(wayid), :params => xml, :headers => auth_header
1095       assert_response :too_many_requests, "way delete did not hit rate limit"
1096
1097       # try creating a way, which should be rate limited
1098       xml = <<~OSM
1099         <osm>
1100           <way changeset='#{changeset.id}'>
1101             <nd ref='#{node1.id}'/>
1102             <nd ref='#{node2.id}'/>
1103           </way>
1104         </osm>
1105       OSM
1106       post api_ways_path, :params => xml, :headers => auth_header
1107       assert_response :too_many_requests, "way create did not hit rate limit"
1108     end
1109
1110     ##
1111     # test maximum rate limit
1112     def test_maximum_rate_limit
1113       # create a user
1114       user = create(:user)
1115
1116       # create some nodes
1117       node1 = create(:node)
1118       node2 = create(:node)
1119
1120       # create a changeset to establish our initial edit time
1121       changeset = create(:changeset, :user => user,
1122                                      :created_at => Time.now.utc - 28.days)
1123
1124       # create changeset to put us near the maximum rate limit
1125       total_changes = Settings.max_changes_per_hour - 1
1126       while total_changes.positive?
1127         changes = [total_changes, Changeset::MAX_ELEMENTS].min
1128         changeset = create(:changeset, :user => user,
1129                                        :created_at => Time.now.utc - 5.minutes,
1130                                        :num_changes => changes)
1131         total_changes -= changes
1132       end
1133
1134       # create authentication header
1135       auth_header = bearer_authorization_header user
1136
1137       # try creating a way
1138       xml = <<~OSM
1139         <osm>
1140           <way changeset='#{changeset.id}'>
1141             <nd ref='#{node1.id}'/>
1142             <nd ref='#{node2.id}'/>
1143           </way>
1144         </osm>
1145       OSM
1146       post api_ways_path, :params => xml, :headers => auth_header
1147       assert_response :success, "way create did not return success status"
1148
1149       # get the id of the way we created
1150       wayid = @response.body
1151
1152       # try updating the way, which should be rate limited
1153       xml = <<~OSM
1154         <osm>
1155           <way id='#{wayid}' version='1' changeset='#{changeset.id}'>
1156             <nd ref='#{node2.id}'/>
1157             <nd ref='#{node1.id}'/>
1158           </way>
1159         </osm>
1160       OSM
1161       put api_way_path(wayid), :params => xml, :headers => auth_header
1162       assert_response :too_many_requests, "way update did not hit rate limit"
1163
1164       # try deleting the way, which should be rate limited
1165       xml = "<osm><way id='#{wayid}' version='2' changeset='#{changeset.id}'/></osm>"
1166       delete api_way_path(wayid), :params => xml, :headers => auth_header
1167       assert_response :too_many_requests, "way delete did not hit rate limit"
1168
1169       # try creating a way, which should be rate limited
1170       xml = <<~OSM
1171         <osm>
1172           <way changeset='#{changeset.id}'>
1173             <nd ref='#{node1.id}'/>
1174             <nd ref='#{node2.id}'/>
1175           </way>
1176         </osm>
1177       OSM
1178       post api_ways_path, :params => xml, :headers => auth_header
1179       assert_response :too_many_requests, "way create did not hit rate limit"
1180     end
1181
1182     private
1183
1184     def affected_models
1185       [Way, WayNode, WayTag,
1186        OldWay, OldWayNode, OldWayTag]
1187     end
1188
1189     ##
1190     # update an attribute in the way element
1191     def xml_attr_rewrite(xml, name, value)
1192       xml.find("//osm/way").first[name] = value.to_s
1193       xml
1194     end
1195
1196     ##
1197     # replace a node in a way element
1198     def xml_replace_node(xml, old_node, new_node)
1199       xml.find("//osm/way/nd[@ref='#{old_node}']").first["ref"] = new_node.to_s
1200       xml
1201     end
1202   end
1203 end