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