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