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