]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets/uploads_controller_test.rb
Add frozen_string_literal comments to ruby files
[rails.git] / test / controllers / api / changesets / uploads_controller_test.rb
1 # frozen_string_literal: true
2
3 require "test_helper"
4
5 module Api
6   module Changesets
7     class UploadsControllerTest < ActionDispatch::IntegrationTest
8       ##
9       # test all routes which lead to this controller
10       def test_routes
11         assert_routing(
12           { :path => "/api/0.6/changeset/1/upload", :method => :post },
13           { :controller => "api/changesets/uploads", :action => "create", :changeset_id => "1" }
14         )
15       end
16
17       def test_upload_when_unauthorized
18         changeset = create(:changeset)
19         node = create(:node, :latitude => 0, :longitude => 0)
20
21         diff = <<~CHANGESET
22           <osmChange>
23             <modify>
24               <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
25             </modify>
26           </osmChange>
27         CHANGESET
28
29         post api_changeset_upload_path(changeset), :params => diff
30
31         assert_response :unauthorized
32
33         assert_no_changes_in changeset
34
35         node.reload
36         assert_equal 1, node.version
37         assert_equal 0, node.latitude
38         assert_equal 0, node.longitude
39       end
40
41       def test_upload_by_private_user
42         user = create(:user, :data_public => false)
43         changeset = create(:changeset, :user => user)
44         node = create(:node, :latitude => 0, :longitude => 0)
45
46         diff = <<~CHANGESET
47           <osmChange>
48             <modify>
49               <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
50             </modify>
51           </osmChange>
52         CHANGESET
53
54         auth_header = bearer_authorization_header user
55
56         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
57
58         assert_response :forbidden
59
60         assert_no_changes_in changeset
61
62         node.reload
63         assert_equal 1, node.version
64         assert_equal 0, node.latitude
65         assert_equal 0, node.longitude
66       end
67
68       def test_upload_without_required_scope
69         user = create(:user)
70         changeset = create(:changeset, :user => user)
71         node = create(:node, :latitude => 0, :longitude => 0)
72
73         diff = <<~CHANGESET
74           <osmChange>
75             <modify>
76               <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
77             </modify>
78           </osmChange>
79         CHANGESET
80
81         auth_header = bearer_authorization_header user, :scopes => %w[read_prefs]
82
83         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
84
85         assert_response :forbidden
86
87         assert_no_changes_in changeset
88
89         node.reload
90         assert_equal 1, node.version
91         assert_equal 0, node.latitude
92         assert_equal 0, node.longitude
93       end
94
95       def test_upload_with_required_scope
96         user = create(:user)
97         changeset = create(:changeset, :user => user)
98         node = create(:node, :latitude => 0, :longitude => 0)
99
100         diff = <<~CHANGESET
101           <osmChange>
102             <modify>
103               <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
104             </modify>
105           </osmChange>
106         CHANGESET
107
108         auth_header = bearer_authorization_header user, :scopes => %w[write_api]
109
110         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
111
112         assert_response :success
113
114         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
115           assert_dom "> node", 1 do
116             assert_dom "> @old_id", node.id.to_s
117             assert_dom "> @new_id", node.id.to_s
118             assert_dom "> @new_version", "2"
119           end
120         end
121
122         changeset.reload
123         assert_equal 1, changeset.num_changes
124         assert_predicate changeset, :num_type_changes_in_sync?
125         assert_equal 1, changeset.num_modified_nodes
126
127         node.reload
128         assert_equal 2, node.version
129         assert_equal 2 * GeoRecord::SCALE, node.latitude
130         assert_equal 1 * GeoRecord::SCALE, node.longitude
131       end
132
133       ##
134       # try to upload with commands other than create, modify, or delete
135       def test_upload_unknown_action
136         changeset = create(:changeset)
137
138         diff = <<~CHANGESET
139           <osmChange>
140             <ping>
141               <node id='1' lon='1' lat='1' changeset='#{changeset.id}' />
142             </ping>
143           </osmChange>
144         CHANGESET
145
146         auth_header = bearer_authorization_header changeset.user
147
148         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
149
150         assert_response :bad_request
151         assert_equal "Unknown action ping, choices are create, modify, delete", @response.body
152
153         assert_no_changes_in changeset
154       end
155
156       ##
157       # test for issues in https://github.com/openstreetmap/trac-tickets/issues/1568
158       def test_upload_empty_changeset
159         changeset = create(:changeset)
160
161         auth_header = bearer_authorization_header changeset.user
162
163         ["<osmChange/>",
164          "<osmChange></osmChange>",
165          "<osmChange><modify/></osmChange>",
166          "<osmChange><modify></modify></osmChange>"].each do |diff|
167           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
168
169           assert_response :success
170         end
171
172         assert_no_changes_in changeset
173       end
174
175       ##
176       # test that the X-Error-Format header works to request XML errors
177       def test_upload_xml_errors
178         changeset = create(:changeset)
179         node = create(:node)
180         create(:relation_member, :member => node)
181
182         # try and delete a node that is in use
183         diff = XML::Document.new
184         diff.root = XML::Node.new "osmChange"
185         delete = XML::Node.new "delete"
186         diff.root << delete
187         delete << xml_node_for_node(node)
188
189         auth_header = bearer_authorization_header changeset.user
190         error_header = error_format_header "xml"
191
192         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header.merge(error_header)
193
194         assert_response :success
195
196         assert_dom "osmError[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1
197         assert_dom "osmError>status", 1
198         assert_dom "osmError>message", 1
199       end
200
201       # -------------------------------------
202       # Test creating elements.
203       # -------------------------------------
204
205       def test_upload_create_node
206         user = create(:user)
207         changeset = create(:changeset, :user => user)
208
209         diff = <<~CHANGESET
210           <osmChange>
211             <create>
212               <node id='-1' lon='30' lat='60' changeset='#{changeset.id}'>
213                 <tag k='amenity' v='bar'/>
214                 <tag k='name' v='Foo'/>
215               </node>
216             </create>
217           </osmChange>
218         CHANGESET
219
220         auth_header = bearer_authorization_header user
221
222         assert_difference "Node.count" => 1,
223                           "OldNode.count" => 1,
224                           "NodeTag.count" => 2,
225                           "OldNodeTag.count" => 2 do
226           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
227
228           assert_response :success
229         end
230
231         node = nil
232         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
233           assert_dom "> node", 1 do |(node_el)|
234             node = Node.find(node_el["new_id"].to_i)
235             assert_dom "> @old_id", "-1"
236             assert_dom "> @new_version", "1"
237           end
238         end
239
240         changeset.reload
241         assert_equal 1, changeset.num_changes
242         assert_predicate changeset, :num_type_changes_in_sync?
243         assert_equal 1, changeset.num_created_nodes
244
245         assert_equal 1, node.version
246         assert_equal changeset, node.changeset
247         assert_predicate node, :visible?
248         assert_equal({ "name" => "Foo", "amenity" => "bar" }, node.tags)
249         assert_equal 60, node.lat
250         assert_equal 30, node.lon
251       end
252
253       def test_upload_create_way
254         node1 = create(:node)
255         node2 = create(:node)
256         user = create(:user)
257         changeset = create(:changeset, :user => user)
258
259         diff = <<~CHANGESET
260           <osmChange>
261             <create>
262               <way id='-1' changeset='#{changeset.id}'>
263                 <tag k='highway' v='primary'/>
264                 <tag k='name' v='Foo'/>
265                 <nd ref='#{node1.id}'/>
266                 <nd ref='#{node2.id}'/>
267               </way>
268             </create>
269           </osmChange>
270         CHANGESET
271
272         auth_header = bearer_authorization_header user
273
274         assert_difference "Way.count" => 1,
275                           "OldWay.count" => 1,
276                           "WayTag.count" => 2,
277                           "OldWayTag.count" => 2,
278                           "WayNode.count" => 2,
279                           "OldWayNode.count" => 2 do
280           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
281
282           assert_response :success
283         end
284
285         way = nil
286         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
287           assert_dom "> way", 1 do |(way_el)|
288             way = Way.find(way_el["new_id"].to_i)
289             assert_dom "> @old_id", "-1"
290             assert_dom "> @new_version", "1"
291           end
292         end
293
294         changeset.reload
295         assert_equal 1, changeset.num_changes
296         assert_predicate changeset, :num_type_changes_in_sync?
297         assert_equal 1, changeset.num_created_ways
298
299         assert_equal 1, way.version
300         assert_equal changeset, way.changeset
301         assert_predicate way, :visible?
302         assert_equal({ "name" => "Foo", "highway" => "primary" }, way.tags)
303         assert_equal [node1, node2], way.nodes
304       end
305
306       def test_upload_create_relation
307         node1 = create(:node)
308         way1 = create(:way_with_nodes)
309         relation1 = create(:relation)
310         user = create(:user)
311         changeset = create(:changeset, :user => user)
312
313         diff = <<~CHANGESET
314           <osmChange>
315             <create>
316               <relation id='-1' changeset='#{changeset.id}'>
317                 <member type='node' role='n_role' ref='#{node1.id}'/>
318                 <member type='way' role='w_role' ref='#{way1.id}'/>
319                 <member type='relation' role='r_role' ref='#{relation1.id}'/>
320                 <tag k='type' v='collection'/>
321               </relation>
322             </create>
323           </osmChange>
324         CHANGESET
325
326         auth_header = bearer_authorization_header user
327
328         assert_difference "Relation.count" => 1,
329                           "OldRelation.count" => 1,
330                           "RelationTag.count" => 1,
331                           "OldRelationTag.count" => 1,
332                           "RelationMember.count" => 3,
333                           "OldRelationMember.count" => 3 do
334           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
335
336           assert_response :success
337         end
338
339         relation = nil
340         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
341           assert_dom "> relation", 1 do |(relation_el)|
342             relation = Relation.find(relation_el["new_id"].to_i)
343             assert_dom "> @old_id", "-1"
344             assert_dom "> @new_version", "1"
345           end
346         end
347
348         changeset.reload
349         assert_equal 1, changeset.num_changes
350         assert_predicate changeset, :num_type_changes_in_sync?
351         assert_equal 1, changeset.num_created_relations
352
353         assert_equal 1, relation.version
354         assert_equal changeset, relation.changeset
355         assert_predicate relation, :visible?
356         assert_equal({ "type" => "collection" }, relation.tags)
357         assert_equal [["Node", node1.id, "n_role"],
358                       ["Way", way1.id, "w_role"],
359                       ["Relation", relation1.id, "r_role"]], relation.members
360       end
361
362       def test_upload_create_elements
363         user = create(:user)
364         changeset = create(:changeset, :user => user)
365         node = create(:node)
366         way = create(:way_with_nodes, :nodes_count => 2)
367         relation = create(:relation)
368
369         diff = <<~CHANGESET
370           <osmChange>
371             <create>
372               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
373                 <tag k='foo' v='bar'/>
374                 <tag k='baz' v='bat'/>
375               </node>
376               <way id='-1' changeset='#{changeset.id}'>
377                 <nd ref='#{node.id}'/>
378               </way>
379             </create>
380             <create>
381               <relation id='-1' changeset='#{changeset.id}'>
382                 <member type='way' role='some' ref='#{way.id}'/>
383                 <member type='node' role='some' ref='#{node.id}'/>
384                 <member type='relation' role='some' ref='#{relation.id}'/>
385               </relation>
386             </create>
387           </osmChange>
388         CHANGESET
389
390         auth_header = bearer_authorization_header user
391
392         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
393
394         assert_response :success
395
396         new_node_id, new_way_id, new_rel_id = nil
397         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
398           # inspect the response to find out what the new element IDs are
399           # check the old IDs are all present and negative one
400           # check the versions are present and equal one
401           assert_dom "> node", 1 do |(node_el)|
402             new_node_id = node_el["new_id"].to_i
403             assert_dom "> @old_id", "-1"
404             assert_dom "> @new_version", "1"
405           end
406           assert_dom "> way", 1 do |(way_el)|
407             new_way_id = way_el["new_id"].to_i
408             assert_dom "> @old_id", "-1"
409             assert_dom "> @new_version", "1"
410           end
411           assert_dom "> relation", 1 do |(rel_el)|
412             new_rel_id = rel_el["new_id"].to_i
413             assert_dom "> @old_id", "-1"
414             assert_dom "> @new_version", "1"
415           end
416         end
417
418         changeset.reload
419         assert_equal 3, changeset.num_changes
420         assert_predicate changeset, :num_type_changes_in_sync?
421         assert_equal 1, changeset.num_created_nodes
422         assert_equal 1, changeset.num_created_ways
423         assert_equal 1, changeset.num_created_relations
424
425         assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
426         assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
427         assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
428       end
429
430       ##
431       # upload an element with a really long tag value
432       def test_upload_create_node_with_tag_too_long
433         changeset = create(:changeset)
434
435         diff = <<~CHANGESET
436           <osmChange>
437             <create>
438               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
439                 <tag k='foo' v='#{'x' * 256}'/>
440               </node>
441             </create>
442           </osmChange>
443         CHANGESET
444
445         auth_header = bearer_authorization_header changeset.user
446
447         assert_no_difference "Node.count" do
448           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
449
450           assert_response :bad_request
451         end
452
453         assert_no_changes_in changeset
454       end
455
456       def test_upload_create_nodes_with_invalid_placeholder_reuse_in_one_action_block
457         changeset = create(:changeset)
458
459         diff = <<~CHANGESET
460           <osmChange>
461             <create>
462               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
463               <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
464             </create>
465           </osmChange>
466         CHANGESET
467
468         auth_header = bearer_authorization_header changeset.user
469
470         assert_no_difference "Node.count" do
471           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
472
473           assert_response :bad_request
474         end
475
476         assert_no_changes_in changeset
477       end
478
479       def test_upload_create_nodes_with_invalid_placeholder_reuse_in_two_action_blocks
480         changeset = create(:changeset)
481
482         diff = <<~CHANGESET
483           <osmChange>
484             <create>
485               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
486             </create>
487             <create>
488               <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
489             </create>
490           </osmChange>
491         CHANGESET
492
493         auth_header = bearer_authorization_header changeset.user
494
495         assert_no_difference "Node.count" do
496           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
497
498           assert_response :bad_request
499         end
500
501         assert_no_changes_in changeset
502       end
503
504       def test_upload_create_way_referring_node_placeholder_defined_later
505         changeset = create(:changeset)
506
507         diff = <<~CHANGESET
508           <osmChange>
509             <create>
510               <way id="-1" changeset="#{changeset.id}">
511                 <nd ref="-1"/>
512               </way>
513               <node id="-1" lat="1" lon="2" changeset="#{changeset.id}"/>
514             </create>
515           </osmChange>
516         CHANGESET
517
518         auth_header = bearer_authorization_header changeset.user
519
520         assert_no_difference "Node.count" do
521           assert_no_difference "Way.count" do
522             post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
523
524             assert_response :bad_request
525           end
526         end
527         assert_equal "Placeholder node not found for reference -1 in way -1", @response.body
528
529         assert_no_changes_in changeset
530       end
531
532       def test_upload_create_way_referring_undefined_node_placeholder
533         changeset = create(:changeset)
534
535         diff = <<~CHANGESET
536           <osmChange>
537             <create>
538               <way id="-1" changeset="#{changeset.id}">
539                 <nd ref="-1"/>
540               </way>
541             </create>
542           </osmChange>
543         CHANGESET
544
545         auth_header = bearer_authorization_header changeset.user
546
547         assert_no_difference "Way.count" do
548           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
549
550           assert_response :bad_request
551         end
552         assert_equal "Placeholder node not found for reference -1 in way -1", @response.body
553
554         assert_no_changes_in changeset
555       end
556
557       def test_upload_create_existing_way_referring_undefined_node_placeholder
558         changeset = create(:changeset)
559         way = create(:way)
560
561         diff = <<~CHANGESET
562           <osmChange>
563             <create>
564               <way id="#{way.id}" changeset="#{changeset.id}" version="1">
565                 <nd ref="-1"/>
566               </way>
567             </create>
568           </osmChange>
569         CHANGESET
570
571         auth_header = bearer_authorization_header changeset.user
572
573         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
574
575         assert_response :bad_request
576         assert_equal "Placeholder node not found for reference -1 in way #{way.id}", @response.body
577
578         assert_no_changes_in changeset
579
580         way.reload
581         assert_equal 1, way.version
582       end
583
584       def test_upload_create_relation_referring_undefined_node_placeholder
585         changeset = create(:changeset)
586
587         diff = <<~CHANGESET
588           <osmChange>
589             <create>
590               <relation id="-1" changeset="#{changeset.id}" version="1">
591                 <member type="node" role="foo" ref="-1"/>
592               </relation>
593             </create>
594           </osmChange>
595         CHANGESET
596
597         auth_header = bearer_authorization_header changeset.user
598
599         assert_no_difference "Relation.count" do
600           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
601
602           assert_response :bad_request
603         end
604         assert_equal "Placeholder Node not found for reference -1 in relation -1.", @response.body
605
606         assert_no_changes_in changeset
607       end
608
609       def test_upload_create_existing_relation_referring_undefined_way_placeholder
610         changeset = create(:changeset)
611         relation = create(:relation)
612
613         diff = <<~CHANGESET
614           <osmChange>
615             <create>
616               <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
617                 <member type="way" role="bar" ref="-1"/>
618               </relation>
619             </create>
620           </osmChange>
621         CHANGESET
622
623         auth_header = bearer_authorization_header changeset.user
624
625         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
626
627         assert_response :bad_request
628         assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
629
630         assert_no_changes_in changeset
631
632         relation.reload
633         assert_equal 1, relation.version
634       end
635
636       def test_upload_create_relations_with_circular_references
637         changeset = create(:changeset)
638
639         diff = <<~CHANGESET
640           <osmChange version='0.6'>
641             <create>
642               <relation id='-2' version='0' changeset='#{changeset.id}'>
643                 <member type='relation' role='' ref='-4' />
644                 <tag k='type' v='route' />
645                 <tag k='name' v='AtoB' />
646               </relation>
647               <relation id='-3' version='0' changeset='#{changeset.id}'>
648                 <tag k='type' v='route' />
649                 <tag k='name' v='BtoA' />
650               </relation>
651               <relation id='-4' version='0' changeset='#{changeset.id}'>
652                 <member type='relation' role='' ref='-2' />
653                 <member type='relation' role='' ref='-3' />
654                 <tag k='type' v='route_master' />
655                 <tag k='name' v='master' />
656               </relation>
657             </create>
658           </osmChange>
659         CHANGESET
660
661         auth_header = bearer_authorization_header changeset.user
662
663         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
664
665         assert_response :bad_request
666         assert_equal "Placeholder Relation not found for reference -4 in relation -2.", @response.body
667
668         assert_no_changes_in changeset
669       end
670
671       # -------------------------------------
672       # Test modifying elements.
673       # -------------------------------------
674
675       def test_upload_modify_node
676         user = create(:user)
677         changeset = create(:changeset, :user => user)
678         node = create(:node, :latitude => 0, :longitude => 0)
679         create(:node_tag, :node => node)
680
681         diff = <<~CHANGESET
682           <osmChange>
683             <modify>
684               <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
685             </modify>
686           </osmChange>
687         CHANGESET
688
689         auth_header = bearer_authorization_header user
690
691         assert_difference "Node.count" => 0,
692                           "OldNode.count" => 1,
693                           "NodeTag.count" => -1,
694                           "OldNodeTag.count" => 0 do
695           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
696
697           assert_response :success
698         end
699
700         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
701           assert_dom "> node", 1 do
702             assert_dom "> @old_id", node.id.to_s
703             assert_dom "> @new_id", node.id.to_s
704             assert_dom "> @new_version", "2"
705           end
706         end
707
708         changeset.reload
709         assert_equal 1, changeset.num_changes
710         assert_predicate changeset, :num_type_changes_in_sync?
711         assert_equal 1, changeset.num_modified_nodes
712
713         node.reload
714         assert_equal 2, node.version
715         assert_equal 2 * GeoRecord::SCALE, node.latitude
716         assert_equal 1 * GeoRecord::SCALE, node.longitude
717         assert_equal 0, node.tags.size, "node #{node.id} should now have no tags"
718       end
719
720       def test_upload_modify_way
721         user = create(:user)
722         changeset = create(:changeset, :user => user)
723         node = create(:node)
724         way = create(:way_with_nodes, :nodes_count => 3)
725         create(:way_tag, :way => way)
726
727         diff = <<~CHANGESET
728           <osmChange>
729             <modify>
730               <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
731                 <nd ref='#{node.id}'/>
732               </way>
733             </modify>
734           </osmChange>
735         CHANGESET
736
737         auth_header = bearer_authorization_header user
738
739         assert_difference "Way.count" => 0,
740                           "OldWay.count" => 1,
741                           "WayTag.count" => -1,
742                           "OldWayTag.count" => 0,
743                           "WayNode.count" => -2,
744                           "OldWayNode.count" => 1 do
745           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
746
747           assert_response :success
748         end
749
750         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
751           assert_dom "> way", 1 do
752             assert_dom "> @old_id", way.id.to_s
753             assert_dom "> @new_id", way.id.to_s
754             assert_dom "> @new_version", "2"
755           end
756         end
757
758         changeset.reload
759         assert_equal 1, changeset.num_changes
760         assert_predicate changeset, :num_type_changes_in_sync?
761         assert_equal 1, changeset.num_modified_ways
762
763         way.reload
764         assert_equal 2, way.version
765         assert_equal 0, way.tags.size, "way #{way.id} should now have no tags"
766         assert_equal [node], way.nodes
767       end
768
769       def test_upload_modify_relation
770         user = create(:user)
771         changeset = create(:changeset, :user => user)
772         node = create(:node)
773         way = create(:way_with_nodes)
774         relation = create(:relation)
775         other_relation = create(:relation)
776         create(:relation_tag, :relation => relation)
777
778         diff = <<~CHANGESET
779           <osmChange>
780             <modify>
781               <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
782                 <member type='way' role='some' ref='#{way.id}'/>
783                 <member type='node' role='some' ref='#{node.id}'/>
784                 <member type='relation' role='some' ref='#{other_relation.id}'/>
785               </relation>
786             </modify>
787           </osmChange>
788         CHANGESET
789
790         auth_header = bearer_authorization_header user
791
792         assert_difference "Relation.count" => 0,
793                           "OldRelation.count" => 1,
794                           "RelationTag.count" => -1,
795                           "OldRelationTag.count" => 0,
796                           "RelationMember.count" => 3,
797                           "OldRelationMember.count" => 3 do
798           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
799
800           assert_response :success
801         end
802
803         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
804           assert_dom "> relation", 1 do
805             assert_dom "> @old_id", relation.id.to_s
806             assert_dom "> @new_id", relation.id.to_s
807             assert_dom "> @new_version", "2"
808           end
809         end
810
811         changeset.reload
812         assert_equal 1, changeset.num_changes
813         assert_predicate changeset, :num_type_changes_in_sync?
814         assert_equal 1, changeset.num_modified_relations
815
816         relation.reload
817         assert_equal 2, relation.version
818         assert_equal 0, relation.tags.size, "relation #{relation.id} should now have no tags"
819         assert_equal [["Way", way.id, "some"], ["Node", node.id, "some"], ["Relation", other_relation.id, "some"]], relation.members
820       end
821
822       def test_upload_modify_elements
823         user = create(:user)
824         changeset = create(:changeset, :user => user)
825         node = create(:node, :latitude => 0, :longitude => 0)
826         way = create(:way)
827         relation = create(:relation)
828         other_relation = create(:relation)
829
830         # create some tags, since we test that they are removed later
831         create(:node_tag, :node => node)
832         create(:way_tag, :way => way)
833         create(:relation_tag, :relation => relation)
834
835         # simple diff to change a node, way and relation by removing their tags
836         diff = <<~CHANGESET
837           <osmChange>
838             <modify>
839               <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
840               <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
841                 <nd ref='#{node.id}'/>
842               </way>
843             </modify>
844             <modify>
845               <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
846                 <member type='way' role='some' ref='#{way.id}'/>
847                 <member type='node' role='some' ref='#{node.id}'/>
848                 <member type='relation' role='some' ref='#{other_relation.id}'/>
849               </relation>
850             </modify>
851           </osmChange>
852         CHANGESET
853
854         auth_header = bearer_authorization_header user
855
856         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
857
858         assert_response :success
859
860         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
861           assert_dom "> node", 1 do
862             assert_dom "> @old_id", node.id.to_s
863             assert_dom "> @new_id", node.id.to_s
864             assert_dom "> @new_version", "2"
865           end
866           assert_dom "> way", 1 do
867             assert_dom "> @old_id", way.id.to_s
868             assert_dom "> @new_id", way.id.to_s
869             assert_dom "> @new_version", "2"
870           end
871           assert_dom "> relation", 1 do
872             assert_dom "> @old_id", relation.id.to_s
873             assert_dom "> @new_id", relation.id.to_s
874             assert_dom "> @new_version", "2"
875           end
876         end
877
878         changeset.reload
879         assert_equal 3, changeset.num_changes
880         assert_predicate changeset, :num_type_changes_in_sync?
881         assert_equal 1, changeset.num_modified_nodes
882         assert_equal 1, changeset.num_modified_ways
883         assert_equal 1, changeset.num_modified_relations
884
885         node.reload
886         assert_equal 2, node.version
887         assert_equal 2 * GeoRecord::SCALE, node.latitude
888         assert_equal 1 * GeoRecord::SCALE, node.longitude
889         assert_equal 0, node.tags.size, "node #{node.id} should now have no tags"
890         way.reload
891         assert_equal 2, way.version
892         assert_equal 0, way.tags.size, "way #{way.id} should now have no tags"
893         assert_equal [node], way.nodes
894         relation.reload
895         assert_equal 2, relation.version
896         assert_equal 0, relation.tags.size, "relation #{relation.id} should now have no tags"
897         assert_equal [["Way", way.id, "some"], ["Node", node.id, "some"], ["Relation", other_relation.id, "some"]], relation.members
898       end
899
900       ##
901       # upload multiple versions of the same element in the same diff.
902       def test_upload_modify_multiple_node_versions
903         node = create(:node)
904         changeset = create(:changeset)
905
906         # change the location of a node multiple times, each time referencing
907         # the last version. doesn't this depend on version numbers being
908         # sequential?
909         diff = <<~CHANGESET
910           <osmChange>
911             <modify>
912               <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset.id}' version='1'/>
913               <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset.id}' version='2'/>
914               <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset.id}' version='3'/>
915               <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset.id}' version='4'/>
916               <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset.id}' version='5'/>
917               <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset.id}' version='6'/>
918               <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset.id}' version='7'/>
919               <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset.id}' version='8'/>
920             </modify>
921           </osmChange>
922         CHANGESET
923
924         auth_header = bearer_authorization_header changeset.user
925
926         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
927
928         assert_response :success
929
930         assert_dom "diffResult>node", 8
931
932         changeset.reload
933         assert_equal 8, changeset.num_changes
934         assert_predicate changeset, :num_type_changes_in_sync?
935         assert_equal 8, changeset.num_modified_nodes
936
937         node.reload
938         assert_equal 9, node.version
939         assert_equal 0.9 * GeoRecord::SCALE, node.latitude
940         assert_equal 0.9 * GeoRecord::SCALE, node.longitude
941       end
942
943       ##
944       # upload multiple versions of the same element in the same diff, but
945       # keep the version numbers the same.
946       def test_upload_modify_duplicate_node_versions
947         node = create(:node, :latitude => 0, :longitude => 0)
948         changeset = create(:changeset)
949
950         diff = <<~CHANGESET
951           <osmChange>
952             <modify>
953               <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
954               <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset.id}' version='1'/>
955             </modify>
956           </osmChange>
957         CHANGESET
958
959         auth_header = bearer_authorization_header changeset.user
960
961         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
962
963         assert_response :conflict
964
965         assert_no_changes_in changeset
966
967         node.reload
968         assert_equal 1, node.version
969         assert_equal 0, node.latitude
970         assert_equal 0, node.longitude
971       end
972
973       ##
974       # try to upload some elements without specifying the version
975       def test_upload_modify_missing_node_version
976         node = create(:node, :latitude => 0, :longitude => 0)
977         changeset = create(:changeset)
978
979         diff = <<~CHANGESET
980           <osmChange>
981             <modify>
982               <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}'/>
983             </modify>
984           </osmChange>
985         CHANGESET
986
987         auth_header = bearer_authorization_header changeset.user
988
989         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
990
991         assert_response :bad_request
992
993         assert_no_changes_in changeset
994
995         node.reload
996         assert_equal 1, node.version
997         assert_equal 0, node.latitude
998         assert_equal 0, node.longitude
999       end
1000
1001       ##
1002       # create a diff which references several changesets, which should cause
1003       # a rollback and none of the diff gets committed
1004       def test_upload_modify_with_references_to_different_changesets
1005         changeset1 = create(:changeset)
1006         changeset2 = create(:changeset, :user => changeset1.user)
1007         node1 = create(:node)
1008         node2 = create(:node)
1009
1010         # simple diff to create a node way and relation using placeholders
1011         diff = <<~CHANGESET
1012           <osmChange>
1013             <modify>
1014               <node id='#{node1.id}' lon='0' lat='0' changeset='#{changeset1.id}' version='1'/>
1015             </modify>
1016             <modify>
1017               <node id='#{node2.id}' lon='0' lat='0' changeset='#{changeset2.id}' version='1'/>
1018             </modify>
1019           </osmChange>
1020         CHANGESET
1021
1022         auth_header = bearer_authorization_header changeset1.user
1023
1024         post api_changeset_upload_path(changeset1), :params => diff, :headers => auth_header
1025
1026         assert_response :conflict
1027
1028         assert_no_changes_in changeset1
1029         assert_no_changes_in changeset2
1030
1031         assert_nodes_are_equal(node1, Node.find(node1.id))
1032         assert_nodes_are_equal(node2, Node.find(node2.id))
1033       end
1034
1035       ##
1036       # upload a valid changeset which has a mixture of whitespace
1037       # to check a bug https://github.com/openstreetmap/trac-tickets/issues/1565
1038       def test_upload_modify_with_mixed_whitespace
1039         changeset = create(:changeset)
1040         node = create(:node)
1041         way = create(:way_with_nodes, :nodes_count => 2)
1042         relation = create(:relation)
1043         other_relation = create(:relation)
1044         create(:relation_tag, :relation => relation)
1045
1046         diff = <<~CHANGESET
1047           <osmChange>
1048           <modify><node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}'
1049             version='1'></node>
1050             <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='2'><tag k='k' v='v'/></node></modify>
1051           <modify>
1052           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'><member
1053             type='way' role='some' ref='#{way.id}'/><member
1054               type='node' role='some' ref='#{node.id}'/>
1055             <member type='relation' role='some' ref='#{other_relation.id}'/>
1056             </relation>
1057           </modify></osmChange>
1058         CHANGESET
1059
1060         auth_header = bearer_authorization_header changeset.user
1061
1062         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1063
1064         assert_response :success
1065
1066         assert_dom "diffResult>node", 2
1067         assert_dom "diffResult>relation", 1
1068
1069         changeset.reload
1070         assert_equal 3, changeset.num_changes
1071         assert_predicate changeset, :num_type_changes_in_sync?
1072         assert_equal 2, changeset.num_modified_nodes
1073         assert_equal 1, changeset.num_modified_relations
1074
1075         assert_equal 1, Node.find(node.id).tags.size, "node #{node.id} should now have one tag"
1076         assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
1077       end
1078
1079       def test_upload_modify_unknown_node_placeholder
1080         check_upload_results_in_not_found do |changeset|
1081           "<modify><node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/></modify>"
1082         end
1083       end
1084
1085       def test_upload_modify_unknown_way_placeholder
1086         check_upload_results_in_not_found do |changeset|
1087           "<modify><way id='-1' changeset='#{changeset.id}' version='1'/></modify>"
1088         end
1089       end
1090
1091       def test_upload_modify_unknown_relation_placeholder
1092         check_upload_results_in_not_found do |changeset|
1093           "<modify><relation id='-1' changeset='#{changeset.id}' version='1'/></modify>"
1094         end
1095       end
1096
1097       # -------------------------------------
1098       # Test deleting elements.
1099       # -------------------------------------
1100
1101       def test_upload_delete_node
1102         changeset = create(:changeset)
1103         node = create(:node, :lat => 0, :lon => 0)
1104         create(:node_tag, :node => node)
1105
1106         diff = <<~CHANGESET
1107           <osmChange>
1108             <delete>
1109               <node id='#{node.id}' changeset='#{changeset.id}' version='1' lat='0' lon='0'/>
1110             </delete>
1111           </osmChange>
1112         CHANGESET
1113
1114         auth_header = bearer_authorization_header changeset.user
1115
1116         assert_difference "Node.count" => 0,
1117                           "OldNode.count" => 1,
1118                           "NodeTag.count" => -1,
1119                           "OldNodeTag.count" => 0 do
1120           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1121
1122           assert_response :success
1123         end
1124
1125         assert_dom "diffResult", 1 do
1126           assert_dom "> node", 1 do
1127             assert_dom "> @old_id", node.id.to_s
1128           end
1129         end
1130
1131         changeset.reload
1132         assert_equal 1, changeset.num_changes
1133         assert_predicate changeset, :num_type_changes_in_sync?
1134         assert_equal 1, changeset.num_deleted_nodes
1135
1136         node.reload
1137         assert_not_predicate node, :visible?
1138       end
1139
1140       def test_upload_delete_way
1141         changeset = create(:changeset)
1142         way = create(:way_with_nodes, :nodes_count => 3)
1143         create(:way_tag, :way => way)
1144
1145         diff = <<~CHANGESET
1146           <osmChange>
1147             <delete>
1148               <way id='#{way.id}' changeset='#{changeset.id}' version='1'/>
1149             </delete>
1150           </osmChange>
1151         CHANGESET
1152
1153         auth_header = bearer_authorization_header changeset.user
1154
1155         assert_difference "Way.count" => 0,
1156                           "OldWay.count" => 1,
1157                           "WayTag.count" => -1,
1158                           "OldWayTag.count" => 0,
1159                           "WayNode.count" => -3,
1160                           "OldWayNode.count" => 0 do
1161           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1162
1163           assert_response :success
1164         end
1165
1166         assert_dom "diffResult", 1 do
1167           assert_dom "> way", 1 do
1168             assert_dom "> @old_id", way.id.to_s
1169           end
1170         end
1171
1172         changeset.reload
1173         assert_equal 1, changeset.num_changes
1174         assert_predicate changeset, :num_type_changes_in_sync?
1175         assert_equal 1, changeset.num_deleted_ways
1176
1177         way.reload
1178         assert_not_predicate way, :visible?
1179       end
1180
1181       def test_upload_delete_relation
1182         changeset = create(:changeset)
1183         relation = create(:relation)
1184         create(:relation_member, :relation => relation, :member => create(:node))
1185         create(:relation_tag, :relation => relation)
1186
1187         diff = <<~CHANGESET
1188           <osmChange>
1189             <delete>
1190               <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'/>
1191             </delete>
1192           </osmChange>
1193         CHANGESET
1194
1195         auth_header = bearer_authorization_header changeset.user
1196
1197         assert_difference "Relation.count" => 0,
1198                           "OldRelation.count" => 1,
1199                           "RelationTag.count" => -1,
1200                           "OldRelationTag.count" => 0,
1201                           "RelationMember.count" => -1,
1202                           "OldRelationMember.count" => 0 do
1203           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1204
1205           assert_response :success
1206         end
1207
1208         assert_dom "diffResult", 1 do
1209           assert_dom "> relation", 1 do
1210             assert_dom "> @old_id", relation.id.to_s
1211           end
1212         end
1213
1214         changeset.reload
1215         assert_equal 1, changeset.num_changes
1216         assert_predicate changeset, :num_type_changes_in_sync?
1217         assert_equal 1, changeset.num_deleted_relations
1218
1219         relation.reload
1220         assert_not_predicate relation, :visible?
1221       end
1222
1223       ##
1224       # test a complex delete where we delete elements which rely on each other
1225       # in the same transaction.
1226       def test_upload_delete_elements
1227         changeset = create(:changeset)
1228         super_relation = create(:relation)
1229         used_relation = create(:relation)
1230         used_way = create(:way)
1231         used_node = create(:node)
1232         create(:relation_member, :relation => super_relation, :member => used_relation)
1233         create(:relation_member, :relation => super_relation, :member => used_way)
1234         create(:relation_member, :relation => super_relation, :member => used_node)
1235
1236         diff = XML::Document.new
1237         diff.root = XML::Node.new "osmChange"
1238         delete = XML::Node.new "delete"
1239         diff.root << delete
1240         delete << xml_node_for_relation(super_relation)
1241         delete << xml_node_for_relation(used_relation)
1242         delete << xml_node_for_way(used_way)
1243         delete << xml_node_for_node(used_node)
1244         %w[node way relation].each do |type|
1245           delete.find("//osmChange/delete/#{type}").each do |n|
1246             n["changeset"] = changeset.id.to_s
1247           end
1248         end
1249
1250         auth_header = bearer_authorization_header changeset.user
1251
1252         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1253
1254         assert_response :success
1255
1256         assert_dom "diffResult", 1 do
1257           assert_dom "> node", 1
1258           assert_dom "> way", 1
1259           assert_dom "> relation", 2
1260         end
1261
1262         changeset.reload
1263         assert_equal 4, changeset.num_changes
1264         assert_predicate changeset, :num_type_changes_in_sync?
1265         assert_equal 1, changeset.num_deleted_nodes
1266         assert_equal 1, changeset.num_deleted_ways
1267         assert_equal 2, changeset.num_deleted_relations
1268
1269         assert_not Node.find(used_node.id).visible
1270         assert_not Way.find(used_way.id).visible
1271         assert_not Relation.find(super_relation.id).visible
1272         assert_not Relation.find(used_relation.id).visible
1273       end
1274
1275       ##
1276       # test uploading a delete with no lat/lon, as they are optional in the osmChange spec.
1277       def test_upload_delete_node_without_latlon
1278         node = create(:node)
1279         changeset = create(:changeset)
1280
1281         diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
1282
1283         auth_header = bearer_authorization_header changeset.user
1284
1285         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1286
1287         assert_response :success
1288
1289         assert_dom "diffResult", 1 do
1290           assert_dom "> node", 1 do
1291             assert_dom "> @old_id", node.id.to_s
1292             assert_dom "> @new_id", 0
1293             assert_dom "> @new_version", 0
1294           end
1295         end
1296
1297         changeset.reload
1298         assert_equal 1, changeset.num_changes
1299         assert_predicate changeset, :num_type_changes_in_sync?
1300         assert_equal 1, changeset.num_deleted_nodes
1301
1302         node.reload
1303         assert_not node.visible
1304       end
1305
1306       ##
1307       # test that deleting stuff in a transaction doesn't bypass the checks
1308       # to ensure that used elements are not deleted.
1309       def test_upload_delete_referenced_elements
1310         changeset = create(:changeset)
1311         relation = create(:relation)
1312         other_relation = create(:relation)
1313         used_way = create(:way)
1314         used_node = create(:node)
1315         create(:relation_member, :relation => relation, :member => used_way)
1316         create(:relation_member, :relation => relation, :member => used_node)
1317
1318         diff = XML::Document.new
1319         diff.root = XML::Node.new "osmChange"
1320         delete = XML::Node.new "delete"
1321         diff.root << delete
1322         delete << xml_node_for_relation(other_relation)
1323         delete << xml_node_for_way(used_way)
1324         delete << xml_node_for_node(used_node)
1325         %w[node way relation].each do |type|
1326           delete.find("//osmChange/delete/#{type}").each do |n|
1327             n["changeset"] = changeset.id.to_s
1328           end
1329         end
1330
1331         auth_header = bearer_authorization_header changeset.user
1332
1333         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1334
1335         assert_response :precondition_failed
1336         assert_equal "Precondition failed: Way #{used_way.id} is still used by relations #{relation.id}.", @response.body
1337
1338         assert_no_changes_in changeset
1339
1340         assert Node.find(used_node.id).visible
1341         assert Way.find(used_way.id).visible
1342         assert Relation.find(relation.id).visible
1343         assert Relation.find(other_relation.id).visible
1344       end
1345
1346       ##
1347       # test that a conditional delete of an in use object works.
1348       def test_upload_delete_if_unused
1349         changeset = create(:changeset)
1350         super_relation = create(:relation)
1351         used_relation = create(:relation)
1352         used_way = create(:way)
1353         used_node = create(:node)
1354         create(:relation_member, :relation => super_relation, :member => used_relation)
1355         create(:relation_member, :relation => super_relation, :member => used_way)
1356         create(:relation_member, :relation => super_relation, :member => used_node)
1357
1358         diff = XML::Document.new
1359         diff.root = XML::Node.new "osmChange"
1360         delete = XML::Node.new "delete"
1361         diff.root << delete
1362         delete["if-unused"] = ""
1363         delete << xml_node_for_relation(used_relation)
1364         delete << xml_node_for_way(used_way)
1365         delete << xml_node_for_node(used_node)
1366         %w[node way relation].each do |type|
1367           delete.find("//osmChange/delete/#{type}").each do |n|
1368             n["changeset"] = changeset.id.to_s
1369           end
1370         end
1371
1372         auth_header = bearer_authorization_header changeset.user
1373
1374         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1375
1376         assert_response :success
1377
1378         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
1379           assert_dom "> node", 1 do
1380             assert_dom "> @old_id", used_node.id.to_s
1381             assert_dom "> @new_id", used_node.id.to_s
1382             assert_dom "> @new_version", used_node.version.to_s
1383           end
1384           assert_dom "> way", 1 do
1385             assert_dom "> @old_id", used_way.id.to_s
1386             assert_dom "> @new_id", used_way.id.to_s
1387             assert_dom "> @new_version", used_way.version.to_s
1388           end
1389           assert_dom "> relation", 1 do
1390             assert_dom "> @old_id", used_relation.id.to_s
1391             assert_dom "> @new_id", used_relation.id.to_s
1392             assert_dom "> @new_version", used_relation.version.to_s
1393           end
1394         end
1395
1396         assert_no_changes_in changeset
1397
1398         assert Node.find(used_node.id).visible
1399         assert Way.find(used_way.id).visible
1400         assert Relation.find(used_relation.id).visible
1401       end
1402
1403       def test_upload_delete_with_multiple_blocks_and_if_unused
1404         changeset = create(:changeset)
1405         node = create(:node)
1406         way = create(:way)
1407         create(:way_node, :way => way, :node => node)
1408         alone_node = create(:node)
1409
1410         diff = <<~CHANGESET
1411           <osmChange version='0.6'>
1412             <delete version="0.6">
1413               <node id="#{node.id}" version="#{node.version}" changeset="#{changeset.id}"/>
1414             </delete>
1415             <delete version="0.6" if-unused="true">
1416               <node id="#{alone_node.id}" version="#{alone_node.version}" changeset="#{changeset.id}"/>
1417             </delete>
1418           </osmChange>
1419         CHANGESET
1420
1421         auth_header = bearer_authorization_header changeset.user
1422
1423         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
1424
1425         assert_response :precondition_failed
1426         assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way.id}.", @response.body
1427
1428         assert_no_changes_in changeset
1429       end
1430
1431       def test_upload_delete_unknown_node_placeholder
1432         check_upload_results_in_not_found do |changeset|
1433           "<delete><node id='-1' changeset='#{changeset.id}' version='1'/></delete>"
1434         end
1435       end
1436
1437       def test_upload_delete_unknown_way_placeholder
1438         check_upload_results_in_not_found do |changeset|
1439           "<delete><way id='-1' changeset='#{changeset.id}' version='1'/></delete>"
1440         end
1441       end
1442
1443       def test_upload_delete_unknown_relation_placeholder
1444         check_upload_results_in_not_found do |changeset|
1445           "<delete><relation id='-1' changeset='#{changeset.id}' version='1'/></delete>"
1446         end
1447       end
1448
1449       # -------------------------------------
1450       # Test combined element changes.
1451       # -------------------------------------
1452
1453       ##
1454       # upload something which creates new objects and inserts them into
1455       # existing containers using placeholders.
1456       def test_upload_create_and_insert_elements
1457         way = create(:way)
1458         node = create(:node)
1459         relation = create(:relation)
1460         create(:way_node, :way => way, :node => node)
1461         changeset = create(:changeset)
1462
1463         diff = <<~CHANGESET
1464           <osmChange>
1465             <create>
1466               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1467                 <tag k='foo' v='bar'/>
1468                 <tag k='baz' v='bat'/>
1469               </node>
1470             </create>
1471             <modify>
1472               <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
1473                 <nd ref='-1'/>
1474                 <nd ref='#{node.id}'/>
1475               </way>
1476               <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
1477                 <member type='way' role='some' ref='#{way.id}'/>
1478                 <member type='node' role='some' ref='-1'/>
1479                 <member type='relation' role='some' ref='#{relation.id}'/>
1480               </relation>
1481             </modify>
1482           </osmChange>
1483         CHANGESET
1484
1485         auth_header = bearer_authorization_header changeset.user
1486
1487         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1488
1489         assert_response :success
1490
1491         new_node_id = nil
1492         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
1493           assert_dom "> node", 1 do |(node_el)|
1494             new_node_id = node_el["new_id"].to_i
1495           end
1496           assert_dom "> way", 1
1497           assert_dom "> relation", 1
1498         end
1499
1500         changeset.reload
1501         assert_equal 3, changeset.num_changes
1502         assert_predicate changeset, :num_type_changes_in_sync?
1503         assert_equal 1, changeset.num_created_nodes
1504         assert_equal 1, changeset.num_modified_ways
1505         assert_equal 1, changeset.num_modified_relations
1506
1507         assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
1508         assert_equal [new_node_id, node.id], Way.find(way.id).nds, "way nodes should match"
1509         Relation.find(relation.id).members.each do |type, id, _role|
1510           assert_equal new_node_id, id, "relation should contain new node" if type == "node"
1511         end
1512       end
1513
1514       ##
1515       # test that a placeholder can be reused within the same upload.
1516       def test_upload_create_modify_delete_node_reusing_placeholder
1517         changeset = create(:changeset)
1518
1519         diff = <<~CHANGESET
1520           <osmChange>
1521             <create>
1522               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1523                 <tag k="foo" v="bar"/>
1524               </node>
1525             </create>
1526             <modify>
1527               <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
1528             </modify>
1529             <delete>
1530               <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
1531             </delete>
1532           </osmChange>
1533         CHANGESET
1534
1535         auth_header = bearer_authorization_header changeset.user
1536
1537         assert_difference "Node.count", 1 do
1538           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1539
1540           assert_response :success
1541         end
1542
1543         assert_dom "diffResult>node", 3
1544         assert_dom "diffResult>node[old_id='-1']", 3
1545
1546         changeset.reload
1547         assert_equal 3, changeset.num_changes
1548         assert_predicate changeset, :num_type_changes_in_sync?
1549         assert_equal 1, changeset.num_created_nodes
1550         assert_equal 1, changeset.num_modified_nodes
1551         assert_equal 1, changeset.num_deleted_nodes
1552
1553         node = Node.last
1554         assert_equal 3, node.version
1555         assert_not node.visible
1556       end
1557
1558       def test_upload_create_and_duplicate_delete
1559         changeset = create(:changeset)
1560
1561         diff = <<~CHANGESET
1562           <osmChange>
1563             <create>
1564               <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1565             </create>
1566             <delete>
1567               <node id="-1" version="1" changeset="#{changeset.id}" />
1568               <node id="-1" version="1" changeset="#{changeset.id}" />
1569             </delete>
1570           </osmChange>
1571         CHANGESET
1572
1573         auth_header = bearer_authorization_header changeset.user
1574
1575         assert_no_difference "Node.count" do
1576           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1577
1578           assert_response :gone
1579         end
1580
1581         assert_no_changes_in changeset
1582       end
1583
1584       def test_upload_create_and_duplicate_delete_if_unused
1585         changeset = create(:changeset)
1586
1587         diff = <<~CHANGESET
1588           <osmChange>
1589             <create>
1590               <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1591             </create>
1592             <delete if-unused="true">
1593               <node id="-1" version="1" changeset="#{changeset.id}" />
1594               <node id="-1" version="1" changeset="#{changeset.id}" />
1595             </delete>
1596           </osmChange>
1597         CHANGESET
1598
1599         auth_header = bearer_authorization_header changeset.user
1600
1601         assert_difference "Node.count", 1 do
1602           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1603
1604           assert_response :success
1605         end
1606
1607         assert_dom "diffResult>node", 3
1608         assert_dom "diffResult>node[old_id='-1']", 3
1609         assert_dom "diffResult>node[new_version='1']", 1
1610         assert_dom "diffResult>node[new_version='2']", 1
1611
1612         changeset.reload
1613         assert_equal 2, changeset.num_changes
1614         assert_predicate changeset, :num_type_changes_in_sync?
1615         assert_equal 1, changeset.num_created_nodes
1616         assert_equal 1, changeset.num_deleted_nodes
1617
1618         node = Node.last
1619         assert_equal 2, node.version
1620         assert_not node.visible
1621       end
1622
1623       # -------------------------------------
1624       # Test bounding boxes.
1625       # -------------------------------------
1626
1627       def test_upload_bbox_of_widely_spaced_nodes
1628         user = create(:user)
1629
1630         # create an old changeset to ensure we have the maximum rate limit
1631         create(:changeset, :user => user, :created_at => Time.now.utc - 28.days)
1632
1633         changeset = create(:changeset, :user => user)
1634
1635         # upload some widely-spaced nodes, spiralling positive and negative
1636         diff = <<~CHANGESET
1637           <osmChange>
1638             <create>
1639               <node id='-1' lon='-20' lat='-10' changeset='#{changeset.id}'/>
1640               <node id='-10' lon='20'  lat='10' changeset='#{changeset.id}'/>
1641               <node id='-2' lon='-40' lat='-20' changeset='#{changeset.id}'/>
1642               <node id='-11' lon='40'  lat='20' changeset='#{changeset.id}'/>
1643               <node id='-3' lon='-60' lat='-30' changeset='#{changeset.id}'/>
1644               <node id='-12' lon='60'  lat='30' changeset='#{changeset.id}'/>
1645               <node id='-4' lon='-80' lat='-40' changeset='#{changeset.id}'/>
1646               <node id='-13' lon='80'  lat='40' changeset='#{changeset.id}'/>
1647               <node id='-5' lon='-100' lat='-50' changeset='#{changeset.id}'/>
1648               <node id='-14' lon='100'  lat='50' changeset='#{changeset.id}'/>
1649               <node id='-6' lon='-120' lat='-60' changeset='#{changeset.id}'/>
1650               <node id='-15' lon='120'  lat='60' changeset='#{changeset.id}'/>
1651               <node id='-7' lon='-140' lat='-70' changeset='#{changeset.id}'/>
1652               <node id='-16' lon='140'  lat='70' changeset='#{changeset.id}'/>
1653               <node id='-8' lon='-160' lat='-80' changeset='#{changeset.id}'/>
1654               <node id='-17' lon='160'  lat='80' changeset='#{changeset.id}'/>
1655               <node id='-9' lon='-179.9' lat='-89.9' changeset='#{changeset.id}'/>
1656               <node id='-18' lon='179.9'  lat='89.9' changeset='#{changeset.id}'/>
1657             </create>
1658           </osmChange>
1659         CHANGESET
1660
1661         auth_header = bearer_authorization_header user
1662
1663         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1664
1665         assert_response :success
1666
1667         changeset.reload
1668         assert_equal 18, changeset.num_changes
1669         assert_predicate changeset, :num_type_changes_in_sync?
1670         assert_equal 18, changeset.num_created_nodes
1671
1672         # check that the changeset bbox is within bounds
1673         assert_operator changeset.min_lon, :>=, -180 * GeoRecord::SCALE, "Minimum longitude (#{changeset.min_lon / GeoRecord::SCALE}) should be >= -180 to be valid."
1674         assert_operator changeset.max_lon, :<=, 180 * GeoRecord::SCALE, "Maximum longitude (#{changeset.max_lon / GeoRecord::SCALE}) should be <= 180 to be valid."
1675         assert_operator changeset.min_lat, :>=, -90 * GeoRecord::SCALE, "Minimum latitude (#{changeset.min_lat / GeoRecord::SCALE}) should be >= -90 to be valid."
1676         assert_operator changeset.max_lat, :<=, 90 * GeoRecord::SCALE, "Maximum latitude (#{changeset.max_lat / GeoRecord::SCALE}) should be <= 90 to be valid."
1677       end
1678
1679       def test_upload_bbox_of_moved_node
1680         changeset = create(:changeset)
1681         node = create(:node, :lat => 1.0, :lon => 2.0)
1682
1683         diff = <<~CHANGESET
1684           <osmChange>
1685             <modify>
1686               <node id='#{node.id}' lat='1.1' lon='2.1' changeset='#{changeset.id}' version='1'/>
1687             </modify>
1688           </osmChange>
1689         CHANGESET
1690
1691         auth_header = bearer_authorization_header changeset.user
1692
1693         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1694
1695         assert_response :success
1696
1697         changeset.reload
1698         assert_equal 1, changeset.num_changes
1699         assert_predicate changeset, :num_type_changes_in_sync?
1700         assert_equal 1, changeset.num_modified_nodes
1701
1702         # check the bbox
1703         assert_equal 1.0 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1.0 degrees"
1704         assert_equal 2.0 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 2.0 degrees"
1705         assert_equal 1.1 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 1.1 degrees"
1706         assert_equal 2.1 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 2.1 degrees"
1707       end
1708
1709       def test_upload_bbox_of_extended_way
1710         way = create(:way)
1711         initial_node = create(:node, :lat => 1.1, :lon => 2.1)
1712         create(:way_node, :way => way, :node => initial_node)
1713         added_node = create(:node, :lat => 1.3, :lon => 2.3)
1714         changeset = create(:changeset)
1715
1716         diff = <<~CHANGESET
1717           <osmChange>
1718             <modify>
1719               <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
1720                 <nd ref='#{initial_node.id}'/>
1721                 <nd ref='#{added_node.id}'/>
1722               </way>
1723             </modify>
1724           </osmChange>
1725         CHANGESET
1726
1727         auth_header = bearer_authorization_header changeset.user
1728
1729         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1730
1731         assert_response :success
1732
1733         changeset.reload
1734         assert_equal 1, changeset.num_changes
1735         assert_predicate changeset, :num_type_changes_in_sync?
1736         assert_equal 1, changeset.num_modified_ways
1737
1738         # check the bbox
1739         assert_equal 1.1 * GeoRecord::SCALE, changeset.min_lat, "min_lat should be 1.1 degrees"
1740         assert_equal 2.1 * GeoRecord::SCALE, changeset.min_lon, "min_lon should be 2.1 degrees"
1741         assert_equal 1.3 * GeoRecord::SCALE, changeset.max_lat, "max_lat should be 1.3 degrees"
1742         assert_equal 2.3 * GeoRecord::SCALE, changeset.max_lon, "max_lon should be 2.3 degrees"
1743       end
1744
1745       # -------------------------------------
1746       # Test upload rate/size limits.
1747       # -------------------------------------
1748
1749       def test_upload_initial_rate_limit
1750         user = create(:user)
1751         node = create(:node)
1752         way = create(:way_with_nodes, :nodes_count => 2)
1753         relation = create(:relation)
1754
1755         # create a changeset that puts us near the initial rate limit
1756         num_changes = Settings.initial_changes_per_hour - 2
1757         changeset = create(:changeset, :user => user,
1758                                        :created_at => Time.now.utc - 5.minutes,
1759                                        :num_changes => num_changes,
1760                                        :num_created_nodes => num_changes)
1761
1762         diff = <<~CHANGESET
1763           <osmChange>
1764             <create>
1765               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1766                 <tag k='foo' v='bar'/>
1767                 <tag k='baz' v='bat'/>
1768               </node>
1769               <way id='-1' changeset='#{changeset.id}'>
1770                 <nd ref='#{node.id}'/>
1771               </way>
1772             </create>
1773             <create>
1774               <relation id='-1' changeset='#{changeset.id}'>
1775                 <member type='way' role='some' ref='#{way.id}'/>
1776                 <member type='node' role='some' ref='#{node.id}'/>
1777                 <member type='relation' role='some' ref='#{relation.id}'/>
1778               </relation>
1779             </create>
1780           </osmChange>
1781         CHANGESET
1782
1783         auth_header = bearer_authorization_header user
1784
1785         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1786
1787         assert_response :too_many_requests, "upload did not hit rate limit"
1788
1789         changeset.reload
1790         assert_equal num_changes, changeset.num_changes
1791         assert_predicate changeset, :num_type_changes_in_sync?
1792       end
1793
1794       def test_upload_maximum_rate_limit
1795         user = create(:user)
1796         node = create(:node)
1797         way = create(:way_with_nodes, :nodes_count => 2)
1798         relation = create(:relation)
1799
1800         # create a changeset to establish our initial edit time
1801         changeset = create(:changeset, :user => user,
1802                                        :created_at => Time.now.utc - 28.days)
1803
1804         # create changeset to put us near the maximum rate limit
1805         remaining_num_changes = Settings.max_changes_per_hour - 2
1806         num_changes = 0
1807         while remaining_num_changes.positive?
1808           num_changes = [remaining_num_changes, Changeset::MAX_ELEMENTS].min
1809           changeset = create(:changeset, :user => user,
1810                                          :created_at => Time.now.utc - 5.minutes,
1811                                          :num_changes => num_changes,
1812                                          :num_created_nodes => num_changes)
1813           remaining_num_changes -= num_changes
1814         end
1815
1816         diff = <<~CHANGESET
1817           <osmChange>
1818             <create>
1819               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
1820                 <tag k='foo' v='bar'/>
1821                 <tag k='baz' v='bat'/>
1822               </node>
1823               <way id='-1' changeset='#{changeset.id}'>
1824                 <nd ref='#{node.id}'/>
1825               </way>
1826             </create>
1827             <create>
1828               <relation id='-1' changeset='#{changeset.id}'>
1829                 <member type='way' role='some' ref='#{way.id}'/>
1830                 <member type='node' role='some' ref='#{node.id}'/>
1831                 <member type='relation' role='some' ref='#{relation.id}'/>
1832               </relation>
1833             </create>
1834           </osmChange>
1835         CHANGESET
1836
1837         auth_header = bearer_authorization_header user
1838
1839         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1840
1841         assert_response :too_many_requests, "upload did not hit rate limit"
1842
1843         changeset.reload
1844         assert_equal num_changes, changeset.num_changes
1845         assert_predicate changeset, :num_type_changes_in_sync?
1846       end
1847
1848       def test_upload_initial_size_limit
1849         user = create(:user)
1850
1851         # create a changeset that puts us near the initial size limit
1852         changeset = create(:changeset, :user => user,
1853                                        :min_lat => (-0.5 * GeoRecord::SCALE).round, :min_lon => (0.5 * GeoRecord::SCALE).round,
1854                                        :max_lat => (0.5 * GeoRecord::SCALE).round, :max_lon => (2.5 * GeoRecord::SCALE).round)
1855
1856         diff = <<~CHANGESET
1857           <osmChange>
1858             <create>
1859               <node id='-1' lon='0.9' lat='2.9' changeset='#{changeset.id}'>
1860                 <tag k='foo' v='bar'/>
1861                 <tag k='baz' v='bat'/>
1862               </node>
1863             </create>
1864           </osmChange>
1865         CHANGESET
1866
1867         auth_header = bearer_authorization_header user
1868
1869         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1870
1871         assert_response :content_too_large, "upload did not hit size limit"
1872
1873         assert_no_changes_in changeset
1874       end
1875
1876       def test_upload_size_limit_after_one_week
1877         user = create(:user)
1878
1879         # create a changeset to establish our initial edit time
1880         create(:changeset, :user => user, :created_at => Time.now.utc - 7.days)
1881
1882         # create a changeset that puts us near the initial size limit
1883         changeset = create(:changeset, :user => user, :bbox => [0.5, -0.5, 2.5, 0.5])
1884
1885         diff = <<~CHANGESET
1886           <osmChange>
1887             <create>
1888               <node id='-1' lon='35' lat='35' changeset='#{changeset.id}'>
1889                 <tag k='foo' v='bar'/>
1890                 <tag k='baz' v='bat'/>
1891               </node>
1892             </create>
1893           </osmChange>
1894         CHANGESET
1895
1896         auth_header = bearer_authorization_header user
1897
1898         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1899
1900         assert_response :content_too_large, "upload did not hit size limit"
1901
1902         assert_no_changes_in changeset
1903       end
1904
1905       private
1906
1907       def check_upload_results_in_not_found(&)
1908         changeset = create(:changeset)
1909         diff = "<osmChange>#{yield changeset}</osmChange>"
1910         auth_header = bearer_authorization_header changeset.user
1911
1912         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1913
1914         assert_response :not_found
1915
1916         assert_no_changes_in changeset
1917       end
1918
1919       def assert_no_changes_in(changeset)
1920         changeset.reload
1921         assert_equal 0, changeset.num_changes
1922         assert_predicate changeset, :num_type_changes_in_sync?
1923       end
1924     end
1925   end
1926 end