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