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}