route_update_test.go
1package waypoint
2
3import (
4 "encoding/json"
5 "testing"
6
7 "git.kilimanjaro.io/ygo"
8)
9
10func TestUpdateWaypointRoutes_NoUpdates(t *testing.T) {
11 doc, err := ygo.NewDoc()
12 if err != nil {
13 t.Fatalf("failed to create doc: %v", err)
14 }
15 defer doc.Destroy()
16
17 // No updates - should return nil
18 err = UpdateWaypointRoutes(doc, nil)
19 if err != nil {
20 t.Fatalf("unexpected error: %v", err)
21 }
22
23 // Empty slice - should return nil
24 err = UpdateWaypointRoutes(doc, []RouteUpdate{})
25 if err != nil {
26 t.Fatalf("unexpected error: %v", err)
27 }
28}
29
30func TestUpdateWaypointRoutes_SingleWaypoint(t *testing.T) {
31 doc, err := ygo.NewDoc()
32 if err != nil {
33 t.Fatalf("failed to create doc: %v", err)
34 }
35 defer doc.Destroy()
36
37 // Get fragment and create waypoint
38 frag, err := doc.GetXmlFragment("main")
39 if err != nil {
40 t.Fatalf("failed to get fragment: %v", err)
41 }
42 defer frag.Destroy()
43
44 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
45 elem, err := frag.InsertElement(txn, 0, "waypoint")
46 if err != nil {
47 return err
48 }
49 defer elem.Destroy()
50
51 idInput, _ := ygo.JSON("wp1")
52 if err := elem.SetAttribute(txn, "id", idInput); err != nil {
53 return err
54 }
55
56 // Set initial empty "from"
57 fromInput, _ := ygo.JSON(map[string]Route{})
58 return elem.SetAttribute(txn, "from", fromInput)
59 })
60 if err != nil {
61 t.Fatalf("failed to setup waypoint: %v", err)
62 }
63
64 // Apply route update
65 updates := []RouteUpdate{
66 {
67 WaypointID: "wp1",
68 RouteKey: "wp0",
69 Route: Route{
70 ID: "abc123",
71 Distance: 100.5,
72 Time: 60.2,
73 Bbox: []float64{-120.0, 38.0, -119.0, 39.0},
74 },
75 },
76 }
77
78 err = UpdateWaypointRoutes(doc, updates)
79 if err != nil {
80 t.Fatalf("failed to update routes: %v", err)
81 }
82
83 // Verify update was applied
84 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
85 childIter, err := frag.ChildIter(txn)
86 if err != nil {
87 return err
88 }
89
90 for {
91 child, err := childIter.Next()
92 if err != nil {
93 return err
94 }
95 if child == nil {
96 break
97 }
98
99 if child.Tag() != ygo.TagXmlElem {
100 child.Destroy()
101 continue
102 }
103
104 xmlElem, err := child.ToXmlElement()
105 child.Destroy()
106 if err != nil {
107 return err
108 }
109 defer xmlElem.Destroy()
110
111 // Get from attribute
112 attrIter, err := xmlElem.AttrIter(txn)
113 if err != nil {
114 return err
115 }
116
117 for {
118 attr, err := attrIter.Next()
119 if err != nil {
120 attrIter.Destroy()
121 return err
122 }
123 if attr == nil {
124 break
125 }
126
127 if attr.Name == "from" {
128 // Read raw JSON bytes and unmarshal directly
129 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
130 var fromMap map[string]Route
131 if err := json.Unmarshal(jsonBytes, &fromMap); err != nil {
132 attr.Destroy()
133 attrIter.Destroy()
134 return err
135 }
136 if routeData, ok := fromMap["wp0"]; ok {
137 if routeData.ID != "abc123" {
138 t.Errorf("expected route ID abc123, got %v", routeData.ID)
139 }
140 if routeData.Distance != 100.5 {
141 t.Errorf("expected distance 100.5, got %v", routeData.Distance)
142 }
143 } else {
144 t.Error("route data not found in from map")
145 }
146 } else {
147 t.Error("from attribute is not valid JSON")
148 }
149 }
150 attr.Destroy()
151 }
152 attrIter.Destroy()
153 }
154
155 return nil
156 })
157 if err != nil {
158 t.Fatalf("failed to verify update: %v", err)
159 }
160}
161
162func TestRouteUpdate_Struct(t *testing.T) {
163 // Test the RouteUpdate struct
164 ru := RouteUpdate{
165 WaypointID: "wp2",
166 RouteKey: "wp1:adv(80)",
167 Route: Route{
168 ID: "def456",
169 Distance: 200.0,
170 Time: 120.0,
171 Bbox: []float64{-121.0, 37.0, -120.0, 38.0},
172 },
173 }
174
175 if ru.WaypointID != "wp2" {
176 t.Error("WaypointID mismatch")
177 }
178 if ru.RouteKey != "wp1:adv(80)" {
179 t.Error("RouteKey mismatch")
180 }
181 if ru.Route.ID != "def456" {
182 t.Error("Route.ID mismatch")
183 }
184}
185
186func TestUpdateWaypointRoutes_WithPointUpdate(t *testing.T) {
187 doc, err := ygo.NewDoc()
188 if err != nil {
189 t.Fatalf("failed to create doc: %v", err)
190 }
191 defer doc.Destroy()
192
193 // Get fragment and create waypoint
194 frag, err := doc.GetXmlFragment("main")
195 if err != nil {
196 t.Fatalf("failed to get fragment: %v", err)
197 }
198 defer frag.Destroy()
199
200 // Set initial point
201 initialPoint := []float64{-120.0, 38.0}
202
203 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
204 elem, err := frag.InsertElement(txn, 0, "waypoint")
205 if err != nil {
206 return err
207 }
208 defer elem.Destroy()
209
210 idInput, _ := ygo.JSON("wp1")
211 if err := elem.SetAttribute(txn, "id", idInput); err != nil {
212 return err
213 }
214
215 pointInput, _ := ygo.JSON(initialPoint)
216 if err := elem.SetAttribute(txn, "point", pointInput); err != nil {
217 return err
218 }
219
220 // Set initial empty "from"
221 fromInput, _ := ygo.JSON(map[string]Route{})
222 return elem.SetAttribute(txn, "from", fromInput)
223 })
224 if err != nil {
225 t.Fatalf("failed to setup waypoint: %v", err)
226 }
227
228 // Apply route update with point snapping
229 newPoint := []float64{-119.5, 38.5}
230 updates := []RouteUpdate{
231 {
232 WaypointID: "wp1",
233 RouteKey: "wp0",
234 Route: Route{
235 ID: "abc123",
236 Distance: 100.5,
237 Time: 60.2,
238 Bbox: []float64{-120.0, 38.0, -119.0, 39.0},
239 },
240 NewPoint: newPoint,
241 },
242 }
243
244 err = UpdateWaypointRoutes(doc, updates)
245 if err != nil {
246 t.Fatalf("failed to update routes: %v", err)
247 }
248
249 // Verify both route and point updates were applied
250 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
251 childIter, err := frag.ChildIter(txn)
252 if err != nil {
253 return err
254 }
255
256 for {
257 child, err := childIter.Next()
258 if err != nil {
259 return err
260 }
261 if child == nil {
262 break
263 }
264
265 if child.Tag() != ygo.TagXmlElem {
266 child.Destroy()
267 continue
268 }
269
270 xmlElem, err := child.ToXmlElement()
271 child.Destroy()
272 if err != nil {
273 return err
274 }
275 defer xmlElem.Destroy()
276
277 // Get attributes
278 attrIter, err := xmlElem.AttrIter(txn)
279 if err != nil {
280 return err
281 }
282
283 var foundFrom, foundPoint bool
284
285 for {
286 attr, err := attrIter.Next()
287 if err != nil {
288 attrIter.Destroy()
289 return err
290 }
291 if attr == nil {
292 break
293 }
294
295 if attr.Name == "from" {
296 foundFrom = true
297 // Read raw JSON bytes and unmarshal directly
298 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
299 var fromMap map[string]Route
300 if err := json.Unmarshal(jsonBytes, &fromMap); err != nil {
301 attr.Destroy()
302 attrIter.Destroy()
303 return err
304 }
305 if routeData, ok := fromMap["wp0"]; ok {
306 if routeData.ID != "abc123" {
307 t.Errorf("expected route ID abc123, got %v", routeData.ID)
308 }
309 } else {
310 t.Error("route data not found in from map")
311 }
312 } else {
313 t.Error("from attribute is not valid JSON")
314 }
315 } else if attr.Name == "point" {
316 foundPoint = true
317 // Read raw JSON bytes and unmarshal directly
318 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
319 var point []float64
320 if err := json.Unmarshal(jsonBytes, &point); err != nil {
321 attr.Destroy()
322 attrIter.Destroy()
323 return err
324 }
325 if len(point) != 2 {
326 t.Errorf("expected point array of length 2, got %d", len(point))
327 } else if point[0] != -119.5 || point[1] != 38.5 {
328 t.Errorf("expected point [-119.5, 38.5], got [%v, %v]", point[0], point[1])
329 }
330 } else {
331 t.Error("point attribute is not valid JSON")
332 }
333 }
334 attr.Destroy()
335 }
336 attrIter.Destroy()
337
338 if !foundFrom {
339 t.Error("from attribute not found")
340 }
341 if !foundPoint {
342 t.Error("point attribute not found")
343 }
344 }
345
346 return nil
347 })
348 if err != nil {
349 t.Fatalf("failed to verify update: %v", err)
350 }
351}
352
353func TestUpdateSourceWaypointPoints_EmptyUpdates(t *testing.T) {
354 doc, err := ygo.NewDoc()
355 if err != nil {
356 t.Fatalf("failed to create doc: %v", err)
357 }
358 defer doc.Destroy()
359
360 // Empty map - should return nil
361 err = UpdateSourceWaypointPoints(doc, map[string][]float64{})
362 if err != nil {
363 t.Fatalf("unexpected error: %v", err)
364 }
365
366 // Nil map - should return nil
367 err = UpdateSourceWaypointPoints(doc, nil)
368 if err != nil {
369 t.Fatalf("unexpected error: %v", err)
370 }
371}
372
373func TestUpdateSourceWaypointPoints_SingleWaypoint(t *testing.T) {
374 doc, err := ygo.NewDoc()
375 if err != nil {
376 t.Fatalf("failed to create doc: %v", err)
377 }
378 defer doc.Destroy()
379
380 // Get fragment and create waypoint
381 frag, err := doc.GetXmlFragment("main")
382 if err != nil {
383 t.Fatalf("failed to get fragment: %v", err)
384 }
385 defer frag.Destroy()
386
387 // Set initial point
388 initialPoint := []float64{-120.0, 38.0}
389
390 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
391 elem, err := frag.InsertElement(txn, 0, "waypoint")
392 if err != nil {
393 return err
394 }
395 defer elem.Destroy()
396
397 idInput, _ := ygo.JSON("wp1")
398 if err := elem.SetAttribute(txn, "id", idInput); err != nil {
399 return err
400 }
401
402 pointInput, _ := ygo.JSON(initialPoint)
403 return elem.SetAttribute(txn, "point", pointInput)
404 })
405 if err != nil {
406 t.Fatalf("failed to setup waypoint: %v", err)
407 }
408
409 // Apply point update
410 newPoint := []float64{-119.5, 38.5}
411 pointUpdates := map[string][]float64{
412 "wp1": newPoint,
413 }
414
415 err = UpdateSourceWaypointPoints(doc, pointUpdates)
416 if err != nil {
417 t.Fatalf("failed to update source waypoint points: %v", err)
418 }
419
420 // Verify update was applied
421 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
422 childIter, err := frag.ChildIter(txn)
423 if err != nil {
424 return err
425 }
426
427 for {
428 child, err := childIter.Next()
429 if err != nil {
430 return err
431 }
432 if child == nil {
433 break
434 }
435
436 if child.Tag() != ygo.TagXmlElem {
437 child.Destroy()
438 continue
439 }
440
441 xmlElem, err := child.ToXmlElement()
442 child.Destroy()
443 if err != nil {
444 return err
445 }
446 defer xmlElem.Destroy()
447
448 // Get point attribute
449 attrIter, err := xmlElem.AttrIter(txn)
450 if err != nil {
451 return err
452 }
453
454 for {
455 attr, err := attrIter.Next()
456 if err != nil {
457 attrIter.Destroy()
458 return err
459 }
460 if attr == nil {
461 break
462 }
463
464 if attr.Name == "point" {
465 // Read raw JSON bytes and unmarshal directly
466 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
467 var point []float64
468 if err := json.Unmarshal(jsonBytes, &point); err != nil {
469 attr.Destroy()
470 attrIter.Destroy()
471 return err
472 }
473 if len(point) != 2 {
474 t.Errorf("expected point array of length 2, got %d", len(point))
475 } else if point[0] != -119.5 || point[1] != 38.5 {
476 t.Errorf("expected point [-119.5, 38.5], got [%v, %v]", point[0], point[1])
477 }
478 } else {
479 t.Error("point attribute is not valid JSON")
480 }
481 }
482 attr.Destroy()
483 }
484 attrIter.Destroy()
485 }
486
487 return nil
488 })
489 if err != nil {
490 t.Fatalf("failed to verify update: %v", err)
491 }
492}
493
494func TestUpdateSourceWaypointPoints_MultipleWaypoints(t *testing.T) {
495 doc, err := ygo.NewDoc()
496 if err != nil {
497 t.Fatalf("failed to create doc: %v", err)
498 }
499 defer doc.Destroy()
500
501 // Get fragment and create multiple waypoints
502 frag, err := doc.GetXmlFragment("main")
503 if err != nil {
504 t.Fatalf("failed to get fragment: %v", err)
505 }
506 defer frag.Destroy()
507
508 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
509 // Create waypoint 1
510 elem1, err := frag.InsertElement(txn, 0, "waypoint")
511 if err != nil {
512 return err
513 }
514 defer elem1.Destroy()
515
516 idInput1, _ := ygo.JSON("wp1")
517 if err := elem1.SetAttribute(txn, "id", idInput1); err != nil {
518 return err
519 }
520 pointInput1, _ := ygo.JSON([]float64{-120.0, 38.0})
521 if err := elem1.SetAttribute(txn, "point", pointInput1); err != nil {
522 return err
523 }
524
525 // Create waypoint 2
526 elem2, err := frag.InsertElement(txn, 1, "waypoint")
527 if err != nil {
528 return err
529 }
530 defer elem2.Destroy()
531
532 idInput2, _ := ygo.JSON("wp2")
533 if err := elem2.SetAttribute(txn, "id", idInput2); err != nil {
534 return err
535 }
536 pointInput2, _ := ygo.JSON([]float64{-121.0, 37.0})
537 if err := elem2.SetAttribute(txn, "point", pointInput2); err != nil {
538 return err
539 }
540
541 // Create waypoint 3 (not in update map)
542 elem3, err := frag.InsertElement(txn, 2, "waypoint")
543 if err != nil {
544 return err
545 }
546 defer elem3.Destroy()
547
548 idInput3, _ := ygo.JSON("wp3")
549 if err := elem3.SetAttribute(txn, "id", idInput3); err != nil {
550 return err
551 }
552 pointInput3, _ := ygo.JSON([]float64{-122.0, 36.0})
553 return elem3.SetAttribute(txn, "point", pointInput3)
554 })
555 if err != nil {
556 t.Fatalf("failed to setup waypoints: %v", err)
557 }
558
559 // Apply point updates for wp1 and wp2 only
560 pointUpdates := map[string][]float64{
561 "wp1": []float64{-119.5, 38.5},
562 "wp2": []float64{-120.5, 37.5},
563 }
564
565 err = UpdateSourceWaypointPoints(doc, pointUpdates)
566 if err != nil {
567 t.Fatalf("failed to update source waypoint points: %v", err)
568 }
569
570 // Verify updates
571 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
572 childIter, err := frag.ChildIter(txn)
573 if err != nil {
574 return err
575 }
576
577 points := make(map[string][]float64)
578
579 for {
580 child, err := childIter.Next()
581 if err != nil {
582 return err
583 }
584 if child == nil {
585 break
586 }
587
588 if child.Tag() != ygo.TagXmlElem {
589 child.Destroy()
590 continue
591 }
592
593 xmlElem, err := child.ToXmlElement()
594 child.Destroy()
595 if err != nil {
596 return err
597 }
598 defer xmlElem.Destroy()
599
600 // Get id and point attributes
601 attrIter, err := xmlElem.AttrIter(txn)
602 if err != nil {
603 return err
604 }
605
606 var id string
607 var point []float64
608
609 for {
610 attr, err := attrIter.Next()
611 if err != nil {
612 attrIter.Destroy()
613 return err
614 }
615 if attr == nil {
616 break
617 }
618
619 if attr.Name == "id" {
620 if str, ok := attr.Value.String(); ok {
621 id = str
622 }
623 } else if attr.Name == "point" {
624 // Read raw JSON bytes and unmarshal directly
625 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
626 if err := json.Unmarshal(jsonBytes, &point); err != nil {
627 attr.Destroy()
628 attrIter.Destroy()
629 return err
630 }
631 if len(point) < 2 {
632 t.Errorf("expected point array of length >= 2, got %d", len(point))
633 }
634 } else {
635 t.Error("point attribute is not valid JSON")
636 }
637 }
638 attr.Destroy()
639 }
640 attrIter.Destroy()
641
642 if id != "" && point != nil {
643 points[id] = point
644 }
645 }
646
647 // Verify wp1 was updated
648 if p, ok := points["wp1"]; !ok || p[0] != -119.5 || p[1] != 38.5 {
649 t.Errorf("wp1: expected [-119.5, 38.5], got %v", p)
650 }
651
652 // Verify wp2 was updated
653 if p, ok := points["wp2"]; !ok || p[0] != -120.5 || p[1] != 37.5 {
654 t.Errorf("wp2: expected [-120.5, 37.5], got %v", p)
655 }
656
657 // Verify wp3 was NOT updated (should still have original value)
658 if p, ok := points["wp3"]; !ok || p[0] != -122.0 || p[1] != 36.0 {
659 t.Errorf("wp3: expected original [-122.0, 36.0], got %v", p)
660 }
661
662 return nil
663 })
664 if err != nil {
665 t.Fatalf("failed to verify updates: %v", err)
666 }
667}
668
669func TestUpdateSourceWaypointPoints_NestedWaypoint(t *testing.T) {
670 doc, err := ygo.NewDoc()
671 if err != nil {
672 t.Fatalf("failed to create doc: %v", err)
673 }
674 defer doc.Destroy()
675
676 // Get fragment and create nested structure
677 frag, err := doc.GetXmlFragment("main")
678 if err != nil {
679 t.Fatalf("failed to get fragment: %v", err)
680 }
681 defer frag.Destroy()
682
683 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
684 // Create a container element
685 container, err := frag.InsertElement(txn, 0, "day")
686 if err != nil {
687 return err
688 }
689 defer container.Destroy()
690
691 // Create waypoint inside container
692 elem, err := container.InsertElement(txn, 0, "waypoint")
693 if err != nil {
694 return err
695 }
696 defer elem.Destroy()
697
698 idInput, _ := ygo.JSON("wp-nested")
699 if err := elem.SetAttribute(txn, "id", idInput); err != nil {
700 return err
701 }
702 pointInput, _ := ygo.JSON([]float64{-120.0, 38.0})
703 return elem.SetAttribute(txn, "point", pointInput)
704 })
705 if err != nil {
706 t.Fatalf("failed to setup nested waypoint: %v", err)
707 }
708
709 // Apply point update
710 newPoint := []float64{-119.5, 38.5}
711 pointUpdates := map[string][]float64{
712 "wp-nested": newPoint,
713 }
714
715 err = UpdateSourceWaypointPoints(doc, pointUpdates)
716 if err != nil {
717 t.Fatalf("failed to update nested waypoint point: %v", err)
718 }
719
720 // Verify update was applied to nested waypoint
721 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
722 childIter, err := frag.ChildIter(txn)
723 if err != nil {
724 return err
725 }
726
727 for {
728 child, err := childIter.Next()
729 if err != nil {
730 return err
731 }
732 if child == nil {
733 break
734 }
735
736 if child.Tag() != ygo.TagXmlElem {
737 child.Destroy()
738 continue
739 }
740
741 xmlElem, err := child.ToXmlElement()
742 child.Destroy()
743 if err != nil {
744 return err
745 }
746 defer xmlElem.Destroy()
747
748 // Get container's children
749 containerIter, err := xmlElem.ChildIter(txn)
750 if err != nil {
751 return err
752 }
753
754 for {
755 containerChild, err := containerIter.Next()
756 if err != nil {
757 containerIter.Destroy()
758 return err
759 }
760 if containerChild == nil {
761 break
762 }
763
764 if containerChild.Tag() != ygo.TagXmlElem {
765 containerChild.Destroy()
766 continue
767 }
768
769 waypointElem, err := containerChild.ToXmlElement()
770 containerChild.Destroy()
771 if err != nil {
772 containerIter.Destroy()
773 return err
774 }
775 defer waypointElem.Destroy()
776
777 // Get point attribute
778 attrIter, err := waypointElem.AttrIter(txn)
779 if err != nil {
780 containerIter.Destroy()
781 return err
782 }
783
784 for {
785 attr, err := attrIter.Next()
786 if err != nil {
787 attrIter.Destroy()
788 containerIter.Destroy()
789 return err
790 }
791 if attr == nil {
792 break
793 }
794
795 if attr.Name == "point" {
796 // Read raw JSON bytes and unmarshal directly
797 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
798 var point []float64
799 if err := json.Unmarshal(jsonBytes, &point); err != nil {
800 attr.Destroy()
801 attrIter.Destroy()
802 containerIter.Destroy()
803 return err
804 }
805 if len(point) != 2 {
806 t.Errorf("expected point array of length 2, got %d", len(point))
807 } else if point[0] != -119.5 || point[1] != 38.5 {
808 t.Errorf("expected point [-119.5, 38.5], got [%v, %v]", point[0], point[1])
809 }
810 } else {
811 t.Error("point attribute is not valid JSON")
812 }
813 }
814 attr.Destroy()
815 }
816 attrIter.Destroy()
817 }
818 containerIter.Destroy()
819 }
820
821 return nil
822 })
823 if err != nil {
824 t.Fatalf("failed to verify update: %v", err)
825 }
826}
827
828func TestUpdateWaypointRoutes_WithoutPointUpdate(t *testing.T) {
829 doc, err := ygo.NewDoc()
830 if err != nil {
831 t.Fatalf("failed to create doc: %v", err)
832 }
833 defer doc.Destroy()
834
835 // Get fragment and create waypoint
836 frag, err := doc.GetXmlFragment("main")
837 if err != nil {
838 t.Fatalf("failed to get fragment: %v", err)
839 }
840 defer frag.Destroy()
841
842 // Set initial point
843 initialPoint := []float64{-120.0, 38.0}
844
845 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
846 elem, err := frag.InsertElement(txn, 0, "waypoint")
847 if err != nil {
848 return err
849 }
850 defer elem.Destroy()
851
852 idInput, _ := ygo.JSON("wp1")
853 if err := elem.SetAttribute(txn, "id", idInput); err != nil {
854 return err
855 }
856
857 pointInput, _ := ygo.JSON(initialPoint)
858 if err := elem.SetAttribute(txn, "point", pointInput); err != nil {
859 return err
860 }
861
862 // Set initial empty "from"
863 fromInput, _ := ygo.JSON(map[string]Route{})
864 return elem.SetAttribute(txn, "from", fromInput)
865 })
866 if err != nil {
867 t.Fatalf("failed to setup waypoint: %v", err)
868 }
869
870 // Apply route update WITHOUT point snapping (NewPoint is nil)
871 updates := []RouteUpdate{
872 {
873 WaypointID: "wp1",
874 RouteKey: "wp0",
875 Route: Route{
876 ID: "abc123",
877 Distance: 100.5,
878 Time: 60.2,
879 Bbox: []float64{-120.0, 38.0, -119.0, 39.0},
880 },
881 NewPoint: nil, // No point update
882 },
883 }
884
885 err = UpdateWaypointRoutes(doc, updates)
886 if err != nil {
887 t.Fatalf("failed to update routes: %v", err)
888 }
889
890 // Verify route was updated but point was NOT changed
891 err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
892 childIter, err := frag.ChildIter(txn)
893 if err != nil {
894 return err
895 }
896
897 for {
898 child, err := childIter.Next()
899 if err != nil {
900 return err
901 }
902 if child == nil {
903 break
904 }
905
906 if child.Tag() != ygo.TagXmlElem {
907 child.Destroy()
908 continue
909 }
910
911 xmlElem, err := child.ToXmlElement()
912 child.Destroy()
913 if err != nil {
914 return err
915 }
916 defer xmlElem.Destroy()
917
918 // Get point attribute
919 attrIter, err := xmlElem.AttrIter(txn)
920 if err != nil {
921 return err
922 }
923
924 for {
925 attr, err := attrIter.Next()
926 if err != nil {
927 attrIter.Destroy()
928 return err
929 }
930 if attr == nil {
931 break
932 }
933
934 if attr.Name == "point" {
935 // Read raw JSON bytes and unmarshal directly
936 if jsonBytes, ok := attr.Value.JSONBytes(); ok {
937 var point []float64
938 if err := json.Unmarshal(jsonBytes, &point); err != nil {
939 attr.Destroy()
940 attrIter.Destroy()
941 return err
942 }
943 if len(point) != 2 {
944 t.Errorf("expected point array of length 2, got %d", len(point))
945 } else if point[0] != -120.0 || point[1] != 38.0 {
946 // Point should still be the initial value
947 t.Errorf("expected original point [-120.0, 38.0], got [%v, %v]", point[0], point[1])
948 }
949 } else {
950 t.Error("point attribute is not valid JSON")
951 }
952 }
953 attr.Destroy()
954 }
955 attrIter.Destroy()
956 }
957
958 return nil
959 })
960 if err != nil {
961 t.Fatalf("failed to verify update: %v", err)
962 }
963}