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