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