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}