route_source_test.go
1package waypoint
2
3import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7 "reflect"
8 "testing"
9
10 "git.kilimanjaro.io/ygo"
11)
12
13func TestFindRouteSource_EmptyDocument(t *testing.T) {
14 doc, err := ygo.NewDoc()
15 if err != nil {
16 t.Fatalf("failed to create doc: %v", err)
17 }
18 defer doc.Destroy()
19
20 // Should not error on empty document
21 if err := FindRouteSource(doc); err != nil {
22 t.Errorf("FindRouteSource failed on empty document: %v", err)
23 }
24}
25
26func TestFindRouteSource_SingleWaypoint(t *testing.T) {
27 doc, err := ygo.NewDoc()
28 if err != nil {
29 t.Fatalf("failed to create doc: %v", err)
30 }
31 defer doc.Destroy()
32
33 // Insert a single waypoint
34 frag, err := doc.GetXmlFragment("main")
35 if err != nil {
36 t.Fatalf("failed to get fragment: %v", err)
37 }
38 defer frag.Destroy()
39
40 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
41 elem, err := frag.InsertElement(txn, 0, "waypoint")
42 if err != nil {
43 return err
44 }
45
46 idInput, _ := ygo.JSON("wp1")
47 elem.SetAttribute(txn, "id", idInput)
48
49 pointInput, _ := ygo.JSON([]float64{10.0, 20.0})
50 elem.SetAttribute(txn, "point", pointInput)
51
52 labelInput, _ := ygo.JSON("Test Point")
53 elem.SetAttribute(txn, "label", labelInput)
54
55 return nil
56 })
57 if err != nil {
58 t.Fatalf("failed to insert waypoint: %v", err)
59 }
60
61 // Run FindRouteSource
62 if err := FindRouteSource(doc); err != nil {
63 t.Fatalf("FindRouteSource failed: %v", err)
64 }
65
66 // Verify the waypoint now has empty "from" array
67 waypoints, err := ExtractWaypoints(doc)
68 if err != nil {
69 t.Fatalf("failed to extract waypoints: %v", err)
70 }
71
72 if len(waypoints) != 1 {
73 t.Fatalf("expected 1 waypoint, got %d", len(waypoints))
74 }
75
76 if len(waypoints[0].From) != 0 {
77 t.Errorf("expected empty From map for first waypoint, got %v", waypoints[0].From)
78 }
79}
80
81func TestFindRouteSource_MultipleWaypoints(t *testing.T) {
82 doc, err := ygo.NewDoc()
83 if err != nil {
84 t.Fatalf("failed to create doc: %v", err)
85 }
86 defer doc.Destroy()
87
88 frag, err := doc.GetXmlFragment("main")
89 if err != nil {
90 t.Fatalf("failed to get fragment: %v", err)
91 }
92 defer frag.Destroy()
93
94 // Insert three waypoints
95 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
96 for i, id := range []string{"wp1", "wp2", "wp3"} {
97 elem, err := frag.InsertElement(txn, uint32(i), "waypoint")
98 if err != nil {
99 return err
100 }
101
102 idInput, _ := ygo.JSON(id)
103 elem.SetAttribute(txn, "id", idInput)
104
105 pointInput, _ := ygo.JSON([]float64{float64(i * 10), float64(i * 20)})
106 elem.SetAttribute(txn, "point", pointInput)
107 }
108 return nil
109 })
110 if err != nil {
111 t.Fatalf("failed to insert waypoints: %v", err)
112 }
113
114 // Run FindRouteSource
115 if err := FindRouteSource(doc); err != nil {
116 t.Fatalf("FindRouteSource failed: %v", err)
117 }
118
119 // Verify the waypoints have correct "from" values
120 waypoints, err := ExtractWaypoints(doc)
121 if err != nil {
122 t.Fatalf("failed to extract waypoints: %v", err)
123 }
124
125 if len(waypoints) != 3 {
126 t.Fatalf("expected 3 waypoints, got %d", len(waypoints))
127 }
128
129 // First waypoint should have empty From
130 if len(waypoints[0].From) != 0 {
131 t.Errorf("expected first waypoint to have empty From, got %v", waypoints[0].From)
132 }
133
134 // Second waypoint should reference first with a route key ending in :saa
135 if len(waypoints[1].From) != 1 {
136 t.Errorf("expected second waypoint to have 1 From entry, got %v", waypoints[1].From)
137 } else {
138 // The key should be "wp1:saa"
139 found := false
140 for key := range waypoints[1].From {
141 if key == "wp1:saa" {
142 found = true
143 break
144 }
145 }
146 if !found {
147 t.Errorf("expected second waypoint From to have key 'wp1:saa', got %v", waypoints[1].From)
148 }
149 }
150
151 // Third waypoint should reference second with a route key ending in :saa
152 if len(waypoints[2].From) != 1 {
153 t.Errorf("expected third waypoint to have 1 From entry, got %v", waypoints[2].From)
154 } else {
155 found := false
156 for key := range waypoints[2].From {
157 if key == "wp2:saa" {
158 found = true
159 break
160 }
161 }
162 if !found {
163 t.Errorf("expected third waypoint From to have key 'wp2:saa', got %v", waypoints[2].From)
164 }
165 }
166}
167
168func TestFindRouteSource_PreservesExistingFrom(t *testing.T) {
169 doc, err := ygo.NewDoc()
170 if err != nil {
171 t.Fatalf("failed to create doc: %v", err)
172 }
173 defer doc.Destroy()
174
175 frag, err := doc.GetXmlFragment("main")
176 if err != nil {
177 t.Fatalf("failed to get fragment: %v", err)
178 }
179 defer frag.Destroy()
180
181 // Insert waypoints with second one already having "from"
182 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
183 // First waypoint
184 elem1, err := frag.InsertElement(txn, 0, "waypoint")
185 if err != nil {
186 return err
187 }
188 idInput1, _ := ygo.JSON("wp1")
189 elem1.SetAttribute(txn, "id", idInput1)
190
191 // Second waypoint with existing "from"
192 elem2, err := frag.InsertElement(txn, 1, "waypoint")
193 if err != nil {
194 return err
195 }
196 idInput2, _ := ygo.JSON("wp2")
197 elem2.SetAttribute(txn, "id", idInput2)
198 fromInput, _ := ygo.JSON(map[string]Route{"custom-id": {}})
199 elem2.SetAttribute(txn, "from", fromInput)
200
201 return nil
202 })
203 if err != nil {
204 t.Fatalf("failed to insert waypoints: %v", err)
205 }
206
207 // Run FindRouteSource
208 if err := FindRouteSource(doc); err != nil {
209 t.Fatalf("FindRouteSource failed: %v", err)
210 }
211
212 // Verify the second waypoint still has its custom "from" value
213 waypoints, err := ExtractWaypoints(doc)
214 if err != nil {
215 t.Fatalf("failed to extract waypoints: %v", err)
216 }
217
218 if len(waypoints) != 2 {
219 t.Fatalf("expected 2 waypoints, got %d", len(waypoints))
220 }
221
222 // Second waypoint should have custom key AND route from wp1
223 if len(waypoints[1].From) != 2 {
224 t.Errorf("expected second waypoint to have 2 From entries (custom + route), got %v", waypoints[1].From)
225 }
226 // Check that the custom key exists
227 if _, exists := waypoints[1].From["custom-id"]; !exists {
228 t.Errorf("expected second waypoint to have From key 'custom-id', got %v", waypoints[1].From)
229 }
230 // Check that route from wp1:saa exists
231 if _, exists := waypoints[1].From["wp1:saa"]; !exists {
232 t.Errorf("expected second waypoint to have From route 'wp1:saa', got %v", waypoints[1].From)
233 }
234}
235
236func TestFindRouteSource_PreservesEmptyArray(t *testing.T) {
237 doc, err := ygo.NewDoc()
238 if err != nil {
239 t.Fatalf("failed to create doc: %v", err)
240 }
241 defer doc.Destroy()
242
243 frag, err := doc.GetXmlFragment("main")
244 if err != nil {
245 t.Fatalf("failed to get fragment: %v", err)
246 }
247 defer frag.Destroy()
248
249 // Insert waypoints with second one having explicit empty "from" array
250 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
251 // First waypoint
252 elem1, err := frag.InsertElement(txn, 0, "waypoint")
253 if err != nil {
254 return err
255 }
256 idInput1, _ := ygo.JSON("wp1")
257 elem1.SetAttribute(txn, "id", idInput1)
258
259 // Second waypoint with explicit empty "from" array
260 elem2, err := frag.InsertElement(txn, 1, "waypoint")
261 if err != nil {
262 return err
263 }
264 idInput2, _ := ygo.JSON("wp2")
265 elem2.SetAttribute(txn, "id", idInput2)
266 fromInput, _ := ygo.JSON(map[string]Route{})
267 elem2.SetAttribute(txn, "from", fromInput)
268
269 return nil
270 })
271 if err != nil {
272 t.Fatalf("failed to insert waypoints: %v", err)
273 }
274
275 // Run FindRouteSource
276 if err := FindRouteSource(doc); err != nil {
277 t.Fatalf("FindRouteSource failed: %v", err)
278 }
279
280 // Verify the second waypoint still has empty "from" array
281 waypoints, err := ExtractWaypoints(doc)
282 if err != nil {
283 t.Fatalf("failed to extract waypoints: %v", err)
284 }
285
286 if len(waypoints) != 2 {
287 t.Fatalf("expected 2 waypoints, got %d", len(waypoints))
288 }
289
290 // Second waypoint should have route from wp1:saa added (existing empty map gets populated)
291 if len(waypoints[1].From) != 1 {
292 t.Errorf("expected second waypoint to have 1 From entry (route from wp1), got %v", waypoints[1].From)
293 }
294 // Check that route from wp1:saa exists
295 if _, exists := waypoints[1].From["wp1:saa"]; !exists {
296 t.Errorf("expected second waypoint to have From route 'wp1:saa', got %v", waypoints[1].From)
297 }
298}
299
300func TestFindRouteSource_RemovesStaleSAARoutes(t *testing.T) {
301 doc, err := ygo.NewDoc()
302 if err != nil {
303 t.Fatalf("failed to create doc: %v", err)
304 }
305 defer doc.Destroy()
306
307 frag, err := doc.GetXmlFragment("main")
308 if err != nil {
309 t.Fatalf("failed to get fragment: %v", err)
310 }
311 defer frag.Destroy()
312
313 // Insert three waypoints with a stale SAA route on wp3
314 // wp1 has no from, wp2 has wp1:saa, wp3 has stale wp1:saa (should be wp2:saa)
315 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
316 // wp1 - first waypoint
317 elem1, err := frag.InsertElement(txn, 0, "waypoint")
318 if err != nil {
319 return err
320 }
321 idInput1, _ := ygo.JSON("wp1")
322 elem1.SetAttribute(txn, "id", idInput1)
323 pointInput1, _ := ygo.JSON([]float64{10.0, 20.0})
324 elem1.SetAttribute(txn, "point", pointInput1)
325
326 // wp2 - second waypoint with correct :saa route
327 elem2, err := frag.InsertElement(txn, 1, "waypoint")
328 if err != nil {
329 return err
330 }
331 idInput2, _ := ygo.JSON("wp2")
332 elem2.SetAttribute(txn, "id", idInput2)
333 pointInput2, _ := ygo.JSON([]float64{30.0, 40.0})
334 elem2.SetAttribute(txn, "point", pointInput2)
335 fromInput2, _ := ygo.JSON(map[string]Route{"wp1:saa": {}})
336 elem2.SetAttribute(txn, "from", fromInput2)
337
338 // wp3 - third waypoint with STALE :saa route (should be wp2:saa, not wp1:saa)
339 elem3, err := frag.InsertElement(txn, 2, "waypoint")
340 if err != nil {
341 return err
342 }
343 idInput3, _ := ygo.JSON("wp3")
344 elem3.SetAttribute(txn, "id", idInput3)
345 pointInput3, _ := ygo.JSON([]float64{50.0, 60.0})
346 elem3.SetAttribute(txn, "point", pointInput3)
347 // This is the stale route - wp3 should route from wp2, not wp1
348 fromInput3, _ := ygo.JSON(map[string]Route{"wp1:saa": {}})
349 elem3.SetAttribute(txn, "from", fromInput3)
350
351 return nil
352 })
353 if err != nil {
354 t.Fatalf("failed to insert waypoints: %v", err)
355 }
356
357 // Run FindRouteSource - it should detect and fix the stale route
358 if err := FindRouteSource(doc); err != nil {
359 t.Fatalf("FindRouteSource failed: %v", err)
360 }
361
362 // Verify the routes are correct
363 waypoints, err := ExtractWaypoints(doc)
364 if err != nil {
365 t.Fatalf("failed to extract waypoints: %v", err)
366 }
367
368 if len(waypoints) != 3 {
369 t.Fatalf("expected 3 waypoints, got %d", len(waypoints))
370 }
371
372 // wp1 (first) should have empty From
373 if len(waypoints[0].From) != 0 {
374 t.Errorf("expected wp1 (first) to have empty From, got %v", waypoints[0].From)
375 }
376
377 // wp2 should have route from wp1:saa
378 if _, exists := waypoints[1].From["wp1:saa"]; !exists {
379 t.Errorf("expected wp2 to have route 'wp1:saa', got %v", waypoints[1].From)
380 }
381
382 // wp3 should have route from wp2:saa (the stale wp1:saa should be replaced)
383 if _, exists := waypoints[2].From["wp2:saa"]; !exists {
384 t.Errorf("expected wp3 to have route 'wp2:saa', got %v", waypoints[2].From)
385 }
386
387 // wp3 should NOT have the stale wp1:saa route
388 if _, exists := waypoints[2].From["wp1:saa"]; exists {
389 t.Errorf("expected wp3 to NOT have stale route 'wp1:saa', got %v", waypoints[2].From)
390 }
391}
392
393func TestFindRouteSource_RemovesSAARoutesFromFirstWaypoint(t *testing.T) {
394 doc, err := ygo.NewDoc()
395 if err != nil {
396 t.Fatalf("failed to create doc: %v", err)
397 }
398 defer doc.Destroy()
399
400 frag, err := doc.GetXmlFragment("main")
401 if err != nil {
402 t.Fatalf("failed to get fragment: %v", err)
403 }
404 defer frag.Destroy()
405
406 // Insert two waypoints where the first one incorrectly has a :saa route
407 // (simulating it was moved to first position)
408 err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
409 // wp1 - first waypoint but with stale :saa route
410 elem1, err := frag.InsertElement(txn, 0, "waypoint")
411 if err != nil {
412 return err
413 }
414 idInput1, _ := ygo.JSON("wp1")
415 elem1.SetAttribute(txn, "id", idInput1)
416 pointInput1, _ := ygo.JSON([]float64{10.0, 20.0})
417 elem1.SetAttribute(txn, "point", pointInput1)
418 // First waypoint shouldn't have any routes, but we're simulating a stale one
419 fromInput1, _ := ygo.JSON(map[string]Route{"oldwp:saa": {}})
420 elem1.SetAttribute(txn, "from", fromInput1)
421
422 // wp2 - second waypoint
423 elem2, err := frag.InsertElement(txn, 1, "waypoint")
424 if err != nil {
425 return err
426 }
427 idInput2, _ := ygo.JSON("wp2")
428 elem2.SetAttribute(txn, "id", idInput2)
429 pointInput2, _ := ygo.JSON([]float64{30.0, 40.0})
430 elem2.SetAttribute(txn, "point", pointInput2)
431
432 return nil
433 })
434 if err != nil {
435 t.Fatalf("failed to insert waypoints: %v", err)
436 }
437
438 // Run FindRouteSource - it should remove the :saa route from the first waypoint
439 if err := FindRouteSource(doc); err != nil {
440 t.Fatalf("FindRouteSource failed: %v", err)
441 }
442
443 // Verify the routes are correct
444 waypoints, err := ExtractWaypoints(doc)
445 if err != nil {
446 t.Fatalf("failed to extract waypoints: %v", err)
447 }
448
449 if len(waypoints) != 2 {
450 t.Fatalf("expected 2 waypoints, got %d", len(waypoints))
451 }
452
453 // wp1 (first) should have empty From (stale :saa route removed)
454 if len(waypoints[0].From) != 0 {
455 t.Errorf("expected wp1 (first) to have empty From after removing stale :saa route, got %v", waypoints[0].From)
456 }
457
458 // wp2 should have route from wp1:saa
459 if _, exists := waypoints[1].From["wp1:saa"]; !exists {
460 t.Errorf("expected wp2 to have route 'wp1:saa', got %v", waypoints[1].From)
461 }
462}
463
464func TestFindRouteSource_FromRealDocument(t *testing.T) {
465 // Load test data from ysweet file
466 testDataPath := filepath.Join("testdata", "test-routefind", "before.ysweet")
467 doc, err := ygo.NewDocFromYSweetFile(testDataPath)
468 if err != nil {
469 t.Fatalf("failed to load ysweet file: %v", err)
470 }
471 defer doc.Destroy()
472
473 // Run FindRouteSource
474 if err := FindRouteSource(doc); err != nil {
475 t.Fatalf("FindRouteSource failed: %v", err)
476 }
477
478 // Extract waypoints from document
479 waypoints, err := ExtractWaypoints(doc)
480 if err != nil {
481 t.Fatalf("failed to extract waypoints: %v", err)
482 }
483
484 // Load expected output
485 expectedPath := filepath.Join("testdata", "test-routefind", "expects.json")
486 expectedData, err := os.ReadFile(expectedPath)
487 if err != nil {
488 t.Fatalf("failed to read expected output: %v", err)
489 }
490
491 var expected []Waypoint
492 if err := json.Unmarshal(expectedData, &expected); err != nil {
493 t.Fatalf("failed to unmarshal expected output: %v", err)
494 }
495
496 // Compare results
497 if len(waypoints) != len(expected) {
498 t.Errorf("expected %d waypoints, got %d", len(expected), len(waypoints))
499 }
500
501 // Compare each waypoint
502 for i, exp := range expected {
503 if i >= len(waypoints) {
504 break
505 }
506 got := waypoints[i]
507
508 if !reflect.DeepEqual(got, exp) {
509 t.Errorf("waypoint %d mismatch:\nexpected: %+v\ngot: %+v", i, exp, got)
510 }
511 }
512}