sync_test.go

  1package waypoint
  2
  3import (
  4	"testing"
  5
  6	"git.kilimanjaro.io/ygo"
  7)
  8
  9func TestSyncToArrayCreatesArray(t *testing.T) {
 10	doc, err := ygo.NewDoc()
 11	if err != nil {
 12		t.Fatalf("failed to create doc: %v", err)
 13	}
 14	defer doc.Destroy()
 15
 16	// Create test waypoints
 17	waypoints := []Waypoint{
 18		{
 19			Type:    "waypoint",
 20			ID:      "wp1",
 21			Point:   []float64{10.0, 20.0},
 22			Label:   "Test Point",
 23			GID:     "test:123",
 24			Country: "US",
 25		},
 26	}
 27
 28	// Sync to array
 29	if err := SyncToArray(doc, "waypoints", waypoints); err != nil {
 30		t.Fatalf("SyncToArray failed: %v", err)
 31	}
 32
 33	// Verify array exists and has correct length
 34	arr, err := doc.GetArray("waypoints")
 35	if err != nil {
 36		t.Fatalf("failed to get array: %v", err)
 37	}
 38	defer arr.Destroy()
 39
 40	var length uint32
 41	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
 42		length = arr.Len()
 43		return nil
 44	})
 45	if err != nil {
 46		t.Fatalf("transaction failed: %v", err)
 47	}
 48
 49	if length != 1 {
 50		t.Errorf("expected array length 1, got %d", length)
 51	}
 52}
 53
 54func TestSyncToArrayDeepComparison(t *testing.T) {
 55	doc, err := ygo.NewDoc()
 56	if err != nil {
 57		t.Fatalf("failed to create doc: %v", err)
 58	}
 59	defer doc.Destroy()
 60
 61	// Initial sync
 62	waypoints1 := []Waypoint{
 63		{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Original", GID: "g1", Country: "US"},
 64	}
 65	if err := SyncToArray(doc, "waypoints", waypoints1); err != nil {
 66		t.Fatalf("first sync failed: %v", err)
 67	}
 68
 69	// Same ID but different label - should trigger update
 70	waypoints2 := []Waypoint{
 71		{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Modified", GID: "g1", Country: "US"},
 72	}
 73	if err := SyncToArray(doc, "waypoints", waypoints2); err != nil {
 74		t.Fatalf("second sync failed: %v", err)
 75	}
 76
 77	// Verify the update was applied by reading back
 78	arr, err := doc.GetArray("waypoints")
 79	if err != nil {
 80		t.Fatalf("failed to get array: %v", err)
 81	}
 82	defer arr.Destroy()
 83
 84	var label string
 85	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
 86		out, err := arr.Get(txn, 0)
 87		if err != nil {
 88			return err
 89		}
 90		defer out.Destroy()
 91
 92		var wp Waypoint
 93		if err := out.JSON(&wp); err != nil {
 94			return err
 95		}
 96		label = wp.Label
 97		return nil
 98	})
 99	if err != nil {
100		t.Fatalf("failed to read waypoint: %v", err)
101	}
102
103	if label != "Modified" {
104		t.Errorf("expected label 'Modified', got %q", label)
105	}
106}
107
108func TestSyncToArrayNoUnnecessaryWrites(t *testing.T) {
109	doc, err := ygo.NewDoc()
110	if err != nil {
111		t.Fatalf("failed to create doc: %v", err)
112	}
113	defer doc.Destroy()
114
115	// Initial sync with 2 waypoints
116	waypoints1 := []Waypoint{
117		{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Point 1", GID: "g1", Country: "US"},
118		{Type: "waypoint", ID: "wp2", Point: []float64{30.0, 40.0}, Label: "Point 2", GID: "g2", Country: "CA"},
119	}
120
121	if err := SyncToArray(doc, "waypoints", waypoints1); err != nil {
122		t.Fatalf("first sync failed: %v", err)
123	}
124
125	// Second sync with identical waypoints (should be no-op for unchanged data)
126	if err := SyncToArray(doc, "waypoints", waypoints1); err != nil {
127		t.Fatalf("second sync failed: %v", err)
128	}
129
130	// Verify array still has 2 elements
131	arr, err := doc.GetArray("waypoints")
132	if err != nil {
133		t.Fatalf("failed to get array: %v", err)
134	}
135	defer arr.Destroy()
136
137	var length uint32
138	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
139		length = arr.Len()
140		return nil
141	})
142	if err != nil {
143		t.Fatalf("transaction failed: %v", err)
144	}
145
146	if length != 2 {
147		t.Errorf("expected array length 2, got %d", length)
148	}
149}
150
151func TestSyncToArrayEmptyWaypoints(t *testing.T) {
152	doc, err := ygo.NewDoc()
153	if err != nil {
154		t.Fatalf("failed to create doc: %v", err)
155	}
156	defer doc.Destroy()
157
158	// First sync with waypoints
159	waypoints := []Waypoint{
160		{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Point 1", GID: "g1", Country: "US"},
161	}
162	if err := SyncToArray(doc, "waypoints", waypoints); err != nil {
163		t.Fatalf("first sync failed: %v", err)
164	}
165
166	// Second sync with empty waypoints
167	if err := SyncToArray(doc, "waypoints", nil); err != nil {
168		t.Fatalf("empty sync failed: %v", err)
169	}
170
171	// Verify array is empty
172	arr, err := doc.GetArray("waypoints")
173	if err != nil {
174		t.Fatalf("failed to get array: %v", err)
175	}
176	defer arr.Destroy()
177
178	var length uint32
179	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
180		length = arr.Len()
181		return nil
182	})
183	if err != nil {
184		t.Fatalf("transaction failed: %v", err)
185	}
186
187	if length != 0 {
188		t.Errorf("expected empty array, got length %d", length)
189	}
190}
191
192func TestSyncToArrayReorderWaypoints(t *testing.T) {
193	doc, err := ygo.NewDoc()
194	if err != nil {
195		t.Fatalf("failed to create doc: %v", err)
196	}
197	defer doc.Destroy()
198
199	// Initial order
200	waypoints1 := []Waypoint{
201		{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Point 1", GID: "g1", Country: "US"},
202		{Type: "waypoint", ID: "wp2", Point: []float64{30.0, 40.0}, Label: "Point 2", GID: "g2", Country: "CA"},
203	}
204	if err := SyncToArray(doc, "waypoints", waypoints1); err != nil {
205		t.Fatalf("first sync failed: %v", err)
206	}
207
208	// Reversed order
209	waypoints2 := []Waypoint{
210		{Type: "waypoint", ID: "wp2", Point: []float64{30.0, 40.0}, Label: "Point 2", GID: "g2", Country: "CA"},
211		{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Point 1", GID: "g1", Country: "US"},
212	}
213	if err := SyncToArray(doc, "waypoints", waypoints2); err != nil {
214		t.Fatalf("reorder sync failed: %v", err)
215	}
216
217	// Verify order changed by reading back
218	arr, err := doc.GetArray("waypoints")
219	if err != nil {
220		t.Fatalf("failed to get array: %v", err)
221	}
222	defer arr.Destroy()
223
224	var firstID, secondID string
225	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
226		if out, err := arr.Get(txn, 0); err == nil {
227			var wp Waypoint
228			if err := out.JSON(&wp); err == nil {
229				firstID = wp.ID
230			}
231			out.Destroy()
232		}
233		if out, err := arr.Get(txn, 1); err == nil {
234			var wp Waypoint
235			if err := out.JSON(&wp); err == nil {
236				secondID = wp.ID
237			}
238			out.Destroy()
239		}
240		return nil
241	})
242	if err != nil {
243		t.Fatalf("transaction failed: %v", err)
244	}
245
246	if firstID != "wp2" || secondID != "wp1" {
247		t.Errorf("expected order [wp2, wp1], got [%s, %s]", firstID, secondID)
248	}
249}
250
251func TestSyncToArrayFromRealDocument(t *testing.T) {
252	// Load test data from ysweet file
253	testDataPath := "testdata/test-waypoints/data.ysweet"
254	doc, err := ygo.NewDocFromYSweetFile(testDataPath)
255	if err != nil {
256		t.Fatalf("failed to load ysweet file: %v", err)
257	}
258	defer doc.Destroy()
259
260	// Extract waypoints from TipTap document
261	waypoints, err := ExtractWaypoints(doc)
262	if err != nil {
263		t.Fatalf("failed to extract waypoints: %v", err)
264	}
265
266	// Sync to array
267	if err := SyncToArray(doc, "extracted_waypoints", waypoints); err != nil {
268		t.Fatalf("sync failed: %v", err)
269	}
270
271	// Verify
272	arr, err := doc.GetArray("extracted_waypoints")
273	if err != nil {
274		t.Fatalf("failed to get array: %v", err)
275	}
276	defer arr.Destroy()
277
278	var length uint32
279	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
280		length = arr.Len()
281		return nil
282	})
283	if err != nil {
284		t.Fatalf("transaction failed: %v", err)
285	}
286
287	if length != 3 {
288		t.Errorf("expected 3 waypoints in array, got %d", length)
289	}
290}
291
292func TestSyncToArrayRemoveMultipleConsecutive(t *testing.T) {
293	doc, err := ygo.NewDoc()
294	if err != nil {
295		t.Fatalf("failed to create doc: %v", err)
296	}
297	defer doc.Destroy()
298
299	// Initial sync with 3 waypoints
300	waypoints1 := []Waypoint{
301		{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Point 1", GID: "g1", Country: "US"},
302		{Type: "waypoint", ID: "wp2", Point: []float64{30.0, 40.0}, Label: "Point 2", GID: "g2", Country: "CA"},
303		{Type: "waypoint", ID: "wp3", Point: []float64{50.0, 60.0}, Label: "Point 3", GID: "g3", Country: "MX"},
304	}
305	if err := SyncToArray(doc, "waypoints", waypoints1); err != nil {
306		t.Fatalf("first sync failed: %v", err)
307	}
308
309	// Remove all waypoints
310	if err := SyncToArray(doc, "waypoints", nil); err != nil {
311		t.Fatalf("empty sync failed: %v", err)
312	}
313
314	// Verify array is empty
315	arr, err := doc.GetArray("waypoints")
316	if err != nil {
317		t.Fatalf("failed to get array: %v", err)
318	}
319	defer arr.Destroy()
320
321	var length uint32
322	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
323		length = arr.Len()
324		return nil
325	})
326	if err != nil {
327		t.Fatalf("transaction failed: %v", err)
328	}
329
330	if length != 0 {
331		t.Errorf("expected empty array after removing all, got length %d", length)
332	}
333}
334
335func TestSyncToArrayRemoveFromMiddle(t *testing.T) {
336	doc, err := ygo.NewDoc()
337	if err != nil {
338		t.Fatalf("failed to create doc: %v", err)
339	}
340	defer doc.Destroy()
341
342	// Initial sync: [A, B, C]
343	waypoints1 := []Waypoint{
344		{Type: "waypoint", ID: "wpA", Point: []float64{10.0, 20.0}, Label: "Point A", GID: "gA", Country: "US"},
345		{Type: "waypoint", ID: "wpB", Point: []float64{30.0, 40.0}, Label: "Point B", GID: "gB", Country: "CA"},
346		{Type: "waypoint", ID: "wpC", Point: []float64{50.0, 60.0}, Label: "Point C", GID: "gC", Country: "MX"},
347	}
348	if err := SyncToArray(doc, "waypoints", waypoints1); err != nil {
349		t.Fatalf("first sync failed: %v", err)
350	}
351
352	// Remove B: [A, C]
353	waypoints2 := []Waypoint{
354		{Type: "waypoint", ID: "wpA", Point: []float64{10.0, 20.0}, Label: "Point A", GID: "gA", Country: "US"},
355		{Type: "waypoint", ID: "wpC", Point: []float64{50.0, 60.0}, Label: "Point C", GID: "gC", Country: "MX"},
356	}
357	if err := SyncToArray(doc, "waypoints", waypoints2); err != nil {
358		t.Fatalf("second sync failed: %v", err)
359	}
360
361	// Verify array has [A, C]
362	arr, err := doc.GetArray("waypoints")
363	if err != nil {
364		t.Fatalf("failed to get array: %v", err)
365	}
366	defer arr.Destroy()
367
368	var ids []string
369	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
370		length := arr.Len()
371		for i := uint32(0); i < length; i++ {
372			out, err := arr.Get(txn, i)
373			if err != nil {
374				return err
375			}
376			var wp Waypoint
377			if err := out.JSON(&wp); err != nil {
378				out.Destroy()
379				return err
380			}
381			ids = append(ids, wp.ID)
382			out.Destroy()
383		}
384		return nil
385	})
386	if err != nil {
387		t.Fatalf("transaction failed: %v", err)
388	}
389
390	if len(ids) != 2 {
391		t.Errorf("expected 2 waypoints, got %d", len(ids))
392	}
393	if len(ids) >= 1 && ids[0] != "wpA" {
394		t.Errorf("expected first waypoint to be wpA, got %s", ids[0])
395	}
396	if len(ids) >= 2 && ids[1] != "wpC" {
397		t.Errorf("expected second waypoint to be wpC, got %s", ids[1])
398	}
399}
400
401func TestSyncToArrayRemoveFromStart(t *testing.T) {
402	doc, err := ygo.NewDoc()
403	if err != nil {
404		t.Fatalf("failed to create doc: %v", err)
405	}
406	defer doc.Destroy()
407
408	// Initial sync: [A, B, C]
409	waypoints1 := []Waypoint{
410		{Type: "waypoint", ID: "wpA", Point: []float64{10.0, 20.0}, Label: "Point A", GID: "gA", Country: "US"},
411		{Type: "waypoint", ID: "wpB", Point: []float64{30.0, 40.0}, Label: "Point B", GID: "gB", Country: "CA"},
412		{Type: "waypoint", ID: "wpC", Point: []float64{50.0, 60.0}, Label: "Point C", GID: "gC", Country: "MX"},
413	}
414	if err := SyncToArray(doc, "waypoints", waypoints1); err != nil {
415		t.Fatalf("first sync failed: %v", err)
416	}
417
418	// Remove A: [B, C]
419	waypoints2 := []Waypoint{
420		{Type: "waypoint", ID: "wpB", Point: []float64{30.0, 40.0}, Label: "Point B", GID: "gB", Country: "CA"},
421		{Type: "waypoint", ID: "wpC", Point: []float64{50.0, 60.0}, Label: "Point C", GID: "gC", Country: "MX"},
422	}
423	if err := SyncToArray(doc, "waypoints", waypoints2); err != nil {
424		t.Fatalf("second sync failed: %v", err)
425	}
426
427	// Verify array has [B, C]
428	arr, err := doc.GetArray("waypoints")
429	if err != nil {
430		t.Fatalf("failed to get array: %v", err)
431	}
432	defer arr.Destroy()
433
434	var ids []string
435	err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
436		length := arr.Len()
437		for i := uint32(0); i < length; i++ {
438			out, err := arr.Get(txn, i)
439			if err != nil {
440				return err
441			}
442			var wp Waypoint
443			if err := out.JSON(&wp); err != nil {
444				out.Destroy()
445				return err
446			}
447			ids = append(ids, wp.ID)
448			out.Destroy()
449		}
450		return nil
451	})
452	if err != nil {
453		t.Fatalf("transaction failed: %v", err)
454	}
455
456	if len(ids) != 2 {
457		t.Errorf("expected 2 waypoints, got %d", len(ids))
458	}
459	if len(ids) >= 1 && ids[0] != "wpB" {
460		t.Errorf("expected first waypoint to be wpB, got %s", ids[0])
461	}
462	if len(ids) >= 2 && ids[1] != "wpC" {
463		t.Errorf("expected second waypoint to be wpC, got %s", ids[1])
464	}
465}
466
467func TestWaypointsEqual(t *testing.T) {
468	tests := []struct {
469		name     string
470		a        Waypoint
471		b        Waypoint
472		expected bool
473	}{
474		{
475			name:     "identical waypoints",
476			a:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
477			b:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
478			expected: true,
479		},
480		{
481			name:     "different label",
482			a:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
483			b:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Different", GID: "g1", Country: "US"},
484			expected: false,
485		},
486		{
487			name:     "different point coordinates",
488			a:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
489			b:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 21.0}, Label: "Test", GID: "g1", Country: "US"},
490			expected: false,
491		},
492		{
493			name:     "different country",
494			a:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
495			b:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "CA"},
496			expected: false,
497		},
498		{
499			name:     "different ID",
500			a:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
501			b:        Waypoint{Type: "waypoint", ID: "wp2", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
502			expected: false,
503		},
504		{
505			name:     "different point length",
506			a:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0, 20.0}, Label: "Test", GID: "g1", Country: "US"},
507			b:        Waypoint{Type: "waypoint", ID: "wp1", Point: []float64{10.0}, Label: "Test", GID: "g1", Country: "US"},
508			expected: false,
509		},
510	}
511
512	for _, tt := range tests {
513		t.Run(tt.name, func(t *testing.T) {
514			result := waypointsEqual(tt.a, tt.b)
515			if result != tt.expected {
516				t.Errorf("waypointsEqual() = %v, expected %v", result, tt.expected)
517			}
518		})
519	}
520}