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