]> git.openstreetmap.org Git - rails.git/blob - test/controllers/api/changesets/uploads_controller_test.rb
Move api empty changeset upload test
[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         changeset.reload
32         assert_equal 0, changeset.num_changes
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         changeset.reload
59         assert_equal 0, changeset.num_changes
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         changeset.reload
86         assert_equal 0, changeset.num_changes
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       end
148
149       ##
150       # test for issues in https://github.com/openstreetmap/trac-tickets/issues/1568
151       def test_upload_empty_changeset
152         changeset = create(:changeset)
153
154         auth_header = bearer_authorization_header changeset.user
155
156         ["<osmChange/>",
157          "<osmChange></osmChange>",
158          "<osmChange><modify/></osmChange>",
159          "<osmChange><modify></modify></osmChange>"].each do |diff|
160           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
161
162           assert_response :success
163         end
164       end
165
166       # -------------------------------------
167       # Test creating elements.
168       # -------------------------------------
169
170       def test_upload_create_elements
171         user = create(:user)
172         changeset = create(:changeset, :user => user)
173         node = create(:node)
174         way = create(:way_with_nodes, :nodes_count => 2)
175         relation = create(:relation)
176
177         diff = <<~CHANGESET
178           <osmChange>
179             <create>
180               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
181                 <tag k='foo' v='bar'/>
182                 <tag k='baz' v='bat'/>
183               </node>
184               <way id='-1' changeset='#{changeset.id}'>
185                 <nd ref='#{node.id}'/>
186               </way>
187             </create>
188             <create>
189               <relation id='-1' changeset='#{changeset.id}'>
190                 <member type='way' role='some' ref='#{way.id}'/>
191                 <member type='node' role='some' ref='#{node.id}'/>
192                 <member type='relation' role='some' ref='#{relation.id}'/>
193               </relation>
194             </create>
195           </osmChange>
196         CHANGESET
197
198         auth_header = bearer_authorization_header user
199
200         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
201
202         assert_response :success
203
204         new_node_id, new_way_id, new_rel_id = nil
205         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
206           # inspect the response to find out what the new element IDs are
207           # check the old IDs are all present and negative one
208           # check the versions are present and equal one
209           assert_dom "> node", 1 do |(node_el)|
210             new_node_id = node_el["new_id"].to_i
211             assert_dom "> @old_id", "-1"
212             assert_dom "> @new_version", "1"
213           end
214           assert_dom "> way", 1 do |(way_el)|
215             new_way_id = way_el["new_id"].to_i
216             assert_dom "> @old_id", "-1"
217             assert_dom "> @new_version", "1"
218           end
219           assert_dom "> relation", 1 do |(rel_el)|
220             new_rel_id = rel_el["new_id"].to_i
221             assert_dom "> @old_id", "-1"
222             assert_dom "> @new_version", "1"
223           end
224         end
225
226         assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
227         assert_equal 0, Way.find(new_way_id).tags.size, "new way should have no tags"
228         assert_equal 0, Relation.find(new_rel_id).tags.size, "new relation should have no tags"
229       end
230
231       ##
232       # upload an element with a really long tag value
233       def test_upload_create_node_with_tag_too_long
234         changeset = create(:changeset)
235
236         diff = <<~CHANGESET
237           <osmChange>
238             <create>
239               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
240                 <tag k='foo' v='#{'x' * 256}'/>
241               </node>
242             </create>
243           </osmChange>
244         CHANGESET
245
246         auth_header = bearer_authorization_header changeset.user
247
248         assert_no_difference "Node.count" do
249           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
250
251           assert_response :bad_request
252         end
253       end
254
255       def test_upload_create_nodes_with_invalid_placeholder_reuse_in_one_action_block
256         changeset = create(:changeset)
257
258         diff = <<~CHANGESET
259           <osmChange>
260             <create>
261               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
262               <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
263             </create>
264           </osmChange>
265         CHANGESET
266
267         auth_header = bearer_authorization_header changeset.user
268
269         assert_no_difference "Node.count" do
270           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
271
272           assert_response :bad_request
273         end
274       end
275
276       def test_upload_create_nodes_with_invalid_placeholder_reuse_in_two_action_blocks
277         changeset = create(:changeset)
278
279         diff = <<~CHANGESET
280           <osmChange>
281             <create>
282               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}' version='1'/>
283             </create>
284             <create>
285               <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
286             </create>
287           </osmChange>
288         CHANGESET
289
290         auth_header = bearer_authorization_header changeset.user
291
292         assert_no_difference "Node.count" do
293           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
294
295           assert_response :bad_request
296         end
297       end
298
299       def test_upload_create_way_referring_node_placeholder_defined_later
300         changeset = create(:changeset)
301
302         diff = <<~CHANGESET
303           <osmChange>
304             <create>
305               <way id="-1" changeset="#{changeset.id}">
306                 <nd ref="-1"/>
307               </way>
308               <node id="-1" lat="1" lon="2" changeset="#{changeset.id}"/>
309             </create>
310           </osmChange>
311         CHANGESET
312
313         auth_header = bearer_authorization_header changeset.user
314
315         assert_no_difference "Node.count" do
316           assert_no_difference "Way.count" do
317             post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
318
319             assert_response :bad_request
320           end
321         end
322         assert_equal "Placeholder node not found for reference -1 in way -1", @response.body
323       end
324
325       def test_upload_create_way_referring_undefined_node_placeholder
326         changeset = create(:changeset)
327
328         diff = <<~CHANGESET
329           <osmChange>
330             <create>
331               <way id="-1" changeset="#{changeset.id}">
332                 <nd ref="-1"/>
333               </way>
334             </create>
335           </osmChange>
336         CHANGESET
337
338         auth_header = bearer_authorization_header changeset.user
339
340         assert_no_difference "Way.count" do
341           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
342
343           assert_response :bad_request
344         end
345         assert_equal "Placeholder node not found for reference -1 in way -1", @response.body
346       end
347
348       def test_upload_create_existing_way_referring_undefined_node_placeholder
349         changeset = create(:changeset)
350         way = create(:way)
351
352         diff = <<~CHANGESET
353           <osmChange>
354             <create>
355               <way id="#{way.id}" changeset="#{changeset.id}" version="1">
356                 <nd ref="-1"/>
357               </way>
358             </create>
359           </osmChange>
360         CHANGESET
361
362         auth_header = bearer_authorization_header changeset.user
363
364         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
365
366         assert_response :bad_request
367         assert_equal "Placeholder node not found for reference -1 in way #{way.id}", @response.body
368
369         way.reload
370         assert_equal 1, way.version
371       end
372
373       def test_upload_create_relation_referring_undefined_node_placeholder
374         changeset = create(:changeset)
375
376         diff = <<~CHANGESET
377           <osmChange>
378             <create>
379               <relation id="-1" changeset="#{changeset.id}" version="1">
380                 <member type="node" role="foo" ref="-1"/>
381               </relation>
382             </create>
383           </osmChange>
384         CHANGESET
385
386         auth_header = bearer_authorization_header changeset.user
387
388         assert_no_difference "Relation.count" do
389           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
390
391           assert_response :bad_request
392         end
393         assert_equal "Placeholder Node not found for reference -1 in relation -1.", @response.body
394       end
395
396       def test_upload_create_existing_relation_referring_undefined_way_placeholder
397         changeset = create(:changeset)
398         relation = create(:relation)
399
400         diff = <<~CHANGESET
401           <osmChange>
402             <create>
403               <relation id="#{relation.id}" changeset="#{changeset.id}" version="1">
404                 <member type="way" role="bar" ref="-1"/>
405               </relation>
406             </create>
407           </osmChange>
408         CHANGESET
409
410         auth_header = bearer_authorization_header changeset.user
411
412         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
413
414         assert_response :bad_request
415         assert_equal "Placeholder Way not found for reference -1 in relation #{relation.id}.", @response.body
416
417         relation.reload
418         assert_equal 1, relation.version
419       end
420
421       def test_upload_create_relations_with_circular_references
422         changeset = create(:changeset)
423
424         diff = <<~CHANGESET
425           <osmChange version='0.6'>
426             <create>
427               <relation id='-2' version='0' changeset='#{changeset.id}'>
428                 <member type='relation' role='' ref='-4' />
429                 <tag k='type' v='route' />
430                 <tag k='name' v='AtoB' />
431               </relation>
432               <relation id='-3' version='0' changeset='#{changeset.id}'>
433                 <tag k='type' v='route' />
434                 <tag k='name' v='BtoA' />
435               </relation>
436               <relation id='-4' version='0' changeset='#{changeset.id}'>
437                 <member type='relation' role='' ref='-2' />
438                 <member type='relation' role='' ref='-3' />
439                 <tag k='type' v='route_master' />
440                 <tag k='name' v='master' />
441               </relation>
442             </create>
443           </osmChange>
444         CHANGESET
445
446         auth_header = bearer_authorization_header changeset.user
447
448         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
449
450         assert_response :bad_request
451         assert_equal "Placeholder Relation not found for reference -4 in relation -2.", @response.body
452       end
453
454       # -------------------------------------
455       # Test modifying elements.
456       # -------------------------------------
457
458       def test_upload_modify_elements
459         user = create(:user)
460         changeset = create(:changeset, :user => user)
461         node = create(:node, :latitude => 0, :longitude => 0)
462         way = create(:way)
463         relation = create(:relation)
464         other_relation = create(:relation)
465
466         # create some tags, since we test that they are removed later
467         create(:node_tag, :node => node)
468         create(:way_tag, :way => way)
469         create(:relation_tag, :relation => relation)
470
471         # simple diff to change a node, way and relation by removing their tags
472         diff = <<~CHANGESET
473           <osmChange>
474             <modify>
475               <node id='#{node.id}' lon='1' lat='2' changeset='#{changeset.id}' version='1'/>
476               <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
477                 <nd ref='#{node.id}'/>
478               </way>
479             </modify>
480             <modify>
481               <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
482                 <member type='way' role='some' ref='#{way.id}'/>
483                 <member type='node' role='some' ref='#{node.id}'/>
484                 <member type='relation' role='some' ref='#{other_relation.id}'/>
485               </relation>
486             </modify>
487           </osmChange>
488         CHANGESET
489
490         auth_header = bearer_authorization_header user
491
492         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
493
494         assert_response :success
495
496         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
497           assert_dom "> node", 1 do
498             assert_dom "> @old_id", node.id.to_s
499             assert_dom "> @new_id", node.id.to_s
500             assert_dom "> @new_version", "2"
501           end
502           assert_dom "> way", 1 do
503             assert_dom "> @old_id", way.id.to_s
504             assert_dom "> @new_id", way.id.to_s
505             assert_dom "> @new_version", "2"
506           end
507           assert_dom "> relation", 1 do
508             assert_dom "> @old_id", relation.id.to_s
509             assert_dom "> @new_id", relation.id.to_s
510             assert_dom "> @new_version", "2"
511           end
512         end
513
514         changeset.reload
515         assert_equal 3, changeset.num_changes
516         node.reload
517         assert_equal 2, node.version
518         assert_equal 2 * GeoRecord::SCALE, node.latitude
519         assert_equal 1 * GeoRecord::SCALE, node.longitude
520         assert_equal 0, node.tags.size, "node #{node.id} should now have no tags"
521         way.reload
522         assert_equal 2, way.version
523         assert_equal 0, way.tags.size, "way #{way.id} should now have no tags"
524         assert_equal [node], way.nodes
525         relation.reload
526         assert_equal 2, relation.version
527         assert_equal 0, relation.tags.size, "relation #{relation.id} should now have no tags"
528         assert_equal [["Way", way.id, "some"], ["Node", node.id, "some"], ["Relation", other_relation.id, "some"]], relation.members
529       end
530
531       ##
532       # upload multiple versions of the same element in the same diff.
533       def test_upload_modify_multiple_node_versions
534         node = create(:node)
535         changeset = create(:changeset)
536
537         # change the location of a node multiple times, each time referencing
538         # the last version. doesn't this depend on version numbers being
539         # sequential?
540         diff = <<~CHANGESET
541           <osmChange>
542             <modify>
543               <node id='#{node.id}' lon='0.0' lat='0.0' changeset='#{changeset.id}' version='1'/>
544               <node id='#{node.id}' lon='0.1' lat='0.0' changeset='#{changeset.id}' version='2'/>
545               <node id='#{node.id}' lon='0.1' lat='0.1' changeset='#{changeset.id}' version='3'/>
546               <node id='#{node.id}' lon='0.1' lat='0.2' changeset='#{changeset.id}' version='4'/>
547               <node id='#{node.id}' lon='0.2' lat='0.2' changeset='#{changeset.id}' version='5'/>
548               <node id='#{node.id}' lon='0.3' lat='0.2' changeset='#{changeset.id}' version='6'/>
549               <node id='#{node.id}' lon='0.3' lat='0.3' changeset='#{changeset.id}' version='7'/>
550               <node id='#{node.id}' lon='0.9' lat='0.9' changeset='#{changeset.id}' version='8'/>
551             </modify>
552           </osmChange>
553         CHANGESET
554
555         auth_header = bearer_authorization_header changeset.user
556
557         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
558
559         assert_response :success
560
561         assert_dom "diffResult>node", 8
562
563         node.reload
564         assert_equal 9, node.version
565         assert_equal 0.9 * GeoRecord::SCALE, node.latitude
566         assert_equal 0.9 * GeoRecord::SCALE, node.longitude
567       end
568
569       ##
570       # upload multiple versions of the same element in the same diff, but
571       # keep the version numbers the same.
572       def test_upload_modify_duplicate_node_versions
573         node = create(:node, :latitude => 0, :longitude => 0)
574         changeset = create(:changeset)
575
576         diff = <<~CHANGESET
577           <osmChange>
578             <modify>
579               <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
580               <node id='#{node.id}' lon='2' lat='2' changeset='#{changeset.id}' version='1'/>
581             </modify>
582           </osmChange>
583         CHANGESET
584
585         auth_header = bearer_authorization_header changeset.user
586
587         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
588
589         assert_response :conflict
590
591         node.reload
592         assert_equal 1, node.version
593         assert_equal 0, node.latitude
594         assert_equal 0, node.longitude
595       end
596
597       ##
598       # try to upload some elements without specifying the version
599       def test_upload_modify_missing_node_version
600         node = create(:node, :latitude => 0, :longitude => 0)
601         changeset = create(:changeset)
602
603         diff = <<~CHANGESET
604           <osmChange>
605             <modify>
606               <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}'/>
607             </modify>
608           </osmChange>
609         CHANGESET
610
611         auth_header = bearer_authorization_header changeset.user
612
613         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
614
615         assert_response :bad_request
616
617         node.reload
618         assert_equal 1, node.version
619         assert_equal 0, node.latitude
620         assert_equal 0, node.longitude
621       end
622
623       ##
624       # create a diff which references several changesets, which should cause
625       # a rollback and none of the diff gets committed
626       def test_upload_modify_with_references_to_different_changesets
627         changeset1 = create(:changeset)
628         changeset2 = create(:changeset, :user => changeset1.user)
629         node1 = create(:node)
630         node2 = create(:node)
631
632         # simple diff to create a node way and relation using placeholders
633         diff = <<~CHANGESET
634           <osmChange>
635             <modify>
636               <node id='#{node1.id}' lon='0' lat='0' changeset='#{changeset1.id}' version='1'/>
637             </modify>
638             <modify>
639               <node id='#{node2.id}' lon='0' lat='0' changeset='#{changeset2.id}' version='1'/>
640             </modify>
641           </osmChange>
642         CHANGESET
643
644         auth_header = bearer_authorization_header changeset1.user
645
646         post api_changeset_upload_path(changeset1), :params => diff, :headers => auth_header
647
648         assert_response :conflict
649
650         assert_nodes_are_equal(node1, Node.find(node1.id))
651         assert_nodes_are_equal(node2, Node.find(node2.id))
652       end
653
654       ##
655       # upload a valid changeset which has a mixture of whitespace
656       # to check a bug https://github.com/openstreetmap/trac-tickets/issues/1565
657       def test_upload_modify_with_mixed_whitespace
658         changeset = create(:changeset)
659         node = create(:node)
660         way = create(:way_with_nodes, :nodes_count => 2)
661         relation = create(:relation)
662         other_relation = create(:relation)
663         create(:relation_tag, :relation => relation)
664
665         diff = <<~CHANGESET
666           <osmChange>
667           <modify><node id='#{node.id}' lon='0' lat='0' changeset='#{changeset.id}'
668             version='1'></node>
669             <node id='#{node.id}' lon='1' lat='1' changeset='#{changeset.id}' version='2'><tag k='k' v='v'/></node></modify>
670           <modify>
671           <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'><member
672             type='way' role='some' ref='#{way.id}'/><member
673               type='node' role='some' ref='#{node.id}'/>
674             <member type='relation' role='some' ref='#{other_relation.id}'/>
675             </relation>
676           </modify></osmChange>
677         CHANGESET
678
679         auth_header = bearer_authorization_header changeset.user
680
681         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
682
683         assert_response :success
684
685         assert_dom "diffResult>node", 2
686         assert_dom "diffResult>relation", 1
687
688         assert_equal 1, Node.find(node.id).tags.size, "node #{node.id} should now have one tag"
689         assert_equal 0, Relation.find(relation.id).tags.size, "relation #{relation.id} should now have no tags"
690       end
691
692       # -------------------------------------
693       # Test deleting elements.
694       # -------------------------------------
695
696       ##
697       # test a complex delete where we delete elements which rely on each other
698       # in the same transaction.
699       def test_upload_delete_elements
700         changeset = create(:changeset)
701         super_relation = create(:relation)
702         used_relation = create(:relation)
703         used_way = create(:way)
704         used_node = create(:node)
705         create(:relation_member, :relation => super_relation, :member => used_relation)
706         create(:relation_member, :relation => super_relation, :member => used_way)
707         create(:relation_member, :relation => super_relation, :member => used_node)
708
709         diff = XML::Document.new
710         diff.root = XML::Node.new "osmChange"
711         delete = XML::Node.new "delete"
712         diff.root << delete
713         delete << xml_node_for_relation(super_relation)
714         delete << xml_node_for_relation(used_relation)
715         delete << xml_node_for_way(used_way)
716         delete << xml_node_for_node(used_node)
717         %w[node way relation].each do |type|
718           delete.find("//osmChange/delete/#{type}").each do |n|
719             n["changeset"] = changeset.id.to_s
720           end
721         end
722
723         auth_header = bearer_authorization_header changeset.user
724
725         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
726
727         assert_response :success
728
729         assert_dom "diffResult", 1 do
730           assert_dom "> node", 1
731           assert_dom "> way", 1
732           assert_dom "> relation", 2
733         end
734
735         assert_not Node.find(used_node.id).visible
736         assert_not Way.find(used_way.id).visible
737         assert_not Relation.find(super_relation.id).visible
738         assert_not Relation.find(used_relation.id).visible
739       end
740
741       ##
742       # test uploading a delete with no lat/lon, as they are optional in the osmChange spec.
743       def test_upload_delete_node_without_latlon
744         node = create(:node)
745         changeset = create(:changeset)
746
747         diff = "<osmChange><delete><node id='#{node.id}' version='#{node.version}' changeset='#{changeset.id}'/></delete></osmChange>"
748
749         auth_header = bearer_authorization_header changeset.user
750
751         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
752
753         assert_response :success
754
755         assert_dom "diffResult", 1 do
756           assert_dom "> node", 1 do
757             assert_dom "> @old_id", node.id.to_s
758             assert_dom "> @new_id", 0
759             assert_dom "> @new_version", 0
760           end
761         end
762
763         node.reload
764         assert_not node.visible
765       end
766
767       ##
768       # test that deleting stuff in a transaction doesn't bypass the checks
769       # to ensure that used elements are not deleted.
770       def test_upload_delete_referenced_elements
771         changeset = create(:changeset)
772         relation = create(:relation)
773         other_relation = create(:relation)
774         used_way = create(:way)
775         used_node = create(:node)
776         create(:relation_member, :relation => relation, :member => used_way)
777         create(:relation_member, :relation => relation, :member => used_node)
778
779         diff = XML::Document.new
780         diff.root = XML::Node.new "osmChange"
781         delete = XML::Node.new "delete"
782         diff.root << delete
783         delete << xml_node_for_relation(other_relation)
784         delete << xml_node_for_way(used_way)
785         delete << xml_node_for_node(used_node)
786         %w[node way relation].each do |type|
787           delete.find("//osmChange/delete/#{type}").each do |n|
788             n["changeset"] = changeset.id.to_s
789           end
790         end
791
792         auth_header = bearer_authorization_header changeset.user
793
794         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
795
796         assert_response :precondition_failed
797         assert_equal "Precondition failed: Way #{used_way.id} is still used by relations #{relation.id}.", @response.body
798
799         assert Node.find(used_node.id).visible
800         assert Way.find(used_way.id).visible
801         assert Relation.find(relation.id).visible
802         assert Relation.find(other_relation.id).visible
803       end
804
805       ##
806       # test that a conditional delete of an in use object works.
807       def test_upload_delete_if_unused
808         changeset = create(:changeset)
809         super_relation = create(:relation)
810         used_relation = create(:relation)
811         used_way = create(:way)
812         used_node = create(:node)
813         create(:relation_member, :relation => super_relation, :member => used_relation)
814         create(:relation_member, :relation => super_relation, :member => used_way)
815         create(:relation_member, :relation => super_relation, :member => used_node)
816
817         diff = XML::Document.new
818         diff.root = XML::Node.new "osmChange"
819         delete = XML::Node.new "delete"
820         diff.root << delete
821         delete["if-unused"] = ""
822         delete << xml_node_for_relation(used_relation)
823         delete << xml_node_for_way(used_way)
824         delete << xml_node_for_node(used_node)
825         %w[node way relation].each do |type|
826           delete.find("//osmChange/delete/#{type}").each do |n|
827             n["changeset"] = changeset.id.to_s
828           end
829         end
830
831         auth_header = bearer_authorization_header changeset.user
832
833         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
834
835         assert_response :success
836
837         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
838           assert_dom "> node", 1 do
839             assert_dom "> @old_id", used_node.id.to_s
840             assert_dom "> @new_id", used_node.id.to_s
841             assert_dom "> @new_version", used_node.version.to_s
842           end
843           assert_dom "> way", 1 do
844             assert_dom "> @old_id", used_way.id.to_s
845             assert_dom "> @new_id", used_way.id.to_s
846             assert_dom "> @new_version", used_way.version.to_s
847           end
848           assert_dom "> relation", 1 do
849             assert_dom "> @old_id", used_relation.id.to_s
850             assert_dom "> @new_id", used_relation.id.to_s
851             assert_dom "> @new_version", used_relation.version.to_s
852           end
853         end
854
855         assert Node.find(used_node.id).visible
856         assert Way.find(used_way.id).visible
857         assert Relation.find(used_relation.id).visible
858       end
859
860       def test_upload_delete_with_multiple_blocks_and_if_unused
861         changeset = create(:changeset)
862         node = create(:node)
863         way = create(:way)
864         create(:way_node, :way => way, :node => node)
865         alone_node = create(:node)
866
867         diff = <<~CHANGESET
868           <osmChange version='0.6'>
869             <delete version="0.6">
870               <node id="#{node.id}" version="#{node.version}" changeset="#{changeset.id}"/>
871             </delete>
872             <delete version="0.6" if-unused="true">
873               <node id="#{alone_node.id}" version="#{alone_node.version}" changeset="#{changeset.id}"/>
874             </delete>
875           </osmChange>
876         CHANGESET
877
878         auth_header = bearer_authorization_header changeset.user
879
880         post api_changeset_upload_path(changeset), :params => diff.to_s, :headers => auth_header
881
882         assert_response :precondition_failed
883
884         assert_equal "Precondition failed: Node #{node.id} is still used by ways #{way.id}.", @response.body
885       end
886
887       # -------------------------------------
888       # Test combined element changes.
889       # -------------------------------------
890
891       ##
892       # upload something which creates new objects and inserts them into
893       # existing containers using placeholders.
894       def test_upload_create_and_insert_elements
895         way = create(:way)
896         node = create(:node)
897         relation = create(:relation)
898         create(:way_node, :way => way, :node => node)
899         changeset = create(:changeset)
900
901         diff = <<~CHANGESET
902           <osmChange>
903             <create>
904               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
905                 <tag k='foo' v='bar'/>
906                 <tag k='baz' v='bat'/>
907               </node>
908             </create>
909             <modify>
910               <way id='#{way.id}' changeset='#{changeset.id}' version='1'>
911                 <nd ref='-1'/>
912                 <nd ref='#{node.id}'/>
913               </way>
914               <relation id='#{relation.id}' changeset='#{changeset.id}' version='1'>
915                 <member type='way' role='some' ref='#{way.id}'/>
916                 <member type='node' role='some' ref='-1'/>
917                 <member type='relation' role='some' ref='#{relation.id}'/>
918               </relation>
919             </modify>
920           </osmChange>
921         CHANGESET
922
923         auth_header = bearer_authorization_header changeset.user
924
925         post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
926
927         assert_response :success
928
929         new_node_id = nil
930         assert_dom "diffResult[version='#{Settings.api_version}'][generator='#{Settings.generator}']", 1 do
931           assert_dom "> node", 1 do |(node_el)|
932             new_node_id = node_el["new_id"].to_i
933           end
934           assert_dom "> way", 1
935           assert_dom "> relation", 1
936         end
937
938         assert_equal 2, Node.find(new_node_id).tags.size, "new node should have two tags"
939         assert_equal [new_node_id, node.id], Way.find(way.id).nds, "way nodes should match"
940         Relation.find(relation.id).members.each do |type, id, _role|
941           assert_equal new_node_id, id, "relation should contain new node" if type == "node"
942         end
943       end
944
945       ##
946       # test that a placeholder can be reused within the same upload.
947       def test_upload_create_modify_delete_node_reusing_placeholder
948         changeset = create(:changeset)
949
950         diff = <<~CHANGESET
951           <osmChange>
952             <create>
953               <node id='-1' lon='0' lat='0' changeset='#{changeset.id}'>
954                 <tag k="foo" v="bar"/>
955               </node>
956             </create>
957             <modify>
958               <node id='-1' lon='1' lat='1' changeset='#{changeset.id}' version='1'/>
959             </modify>
960             <delete>
961               <node id='-1' lon='2' lat='2' changeset='#{changeset.id}' version='2'/>
962             </delete>
963           </osmChange>
964         CHANGESET
965
966         auth_header = bearer_authorization_header changeset.user
967
968         assert_difference "Node.count", 1 do
969           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
970
971           assert_response :success
972         end
973
974         assert_dom "diffResult>node", 3
975         assert_dom "diffResult>node[old_id='-1']", 3
976
977         node = Node.last
978         assert_equal 3, node.version
979         assert_not node.visible
980       end
981
982       def test_upload_create_and_duplicate_delete
983         changeset = create(:changeset)
984
985         diff = <<~CHANGESET
986           <osmChange>
987             <create>
988               <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
989             </create>
990             <delete>
991               <node id="-1" version="1" changeset="#{changeset.id}" />
992               <node id="-1" version="1" changeset="#{changeset.id}" />
993             </delete>
994           </osmChange>
995         CHANGESET
996
997         auth_header = bearer_authorization_header changeset.user
998
999         assert_no_difference "Node.count" do
1000           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1001
1002           assert_response :gone
1003         end
1004       end
1005
1006       def test_upload_create_and_duplicate_delete_if_unused
1007         changeset = create(:changeset)
1008
1009         diff = <<~CHANGESET
1010           <osmChange>
1011             <create>
1012               <node id="-1" lat="39" lon="116" changeset="#{changeset.id}" />
1013             </create>
1014             <delete if-unused="true">
1015               <node id="-1" version="1" changeset="#{changeset.id}" />
1016               <node id="-1" version="1" changeset="#{changeset.id}" />
1017             </delete>
1018           </osmChange>
1019         CHANGESET
1020
1021         auth_header = bearer_authorization_header changeset.user
1022
1023         assert_difference "Node.count", 1 do
1024           post api_changeset_upload_path(changeset), :params => diff, :headers => auth_header
1025
1026           assert_response :success
1027         end
1028
1029         assert_dom "diffResult>node", 3
1030         assert_dom "diffResult>node[old_id='-1']", 3
1031         assert_dom "diffResult>node[new_version='1']", 1
1032         assert_dom "diffResult>node[new_version='2']", 1
1033
1034         node = Node.last
1035         assert_equal 2, node.version
1036         assert_not node.visible
1037       end
1038     end
1039   end
1040 end