[DEPRECATED] Go implementation of plcbundle
1package bundleindex_test
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "sync"
8 "testing"
9 "time"
10
11 "tangled.org/atscan.net/plcbundle-go/internal/bundleindex"
12 "tangled.org/atscan.net/plcbundle-go/internal/types"
13)
14
15type testLogger struct {
16 t *testing.T
17}
18
19func (l *testLogger) Printf(format string, v ...interface{}) {
20 l.t.Logf(format, v...)
21}
22
23func (l *testLogger) Println(v ...interface{}) {
24 l.t.Log(v...)
25}
26
27// ====================================================================================
28// INDEX CREATION & BASIC OPERATIONS
29// ====================================================================================
30
31func TestIndexCreation(t *testing.T) {
32 t.Run("NewIndex", func(t *testing.T) {
33 idx := bundleindex.NewIndex("https://plc.directory")
34
35 if idx == nil {
36 t.Fatal("NewIndex returned nil")
37 }
38
39 if idx.Version != types.INDEX_VERSION {
40 t.Errorf("version mismatch: got %s, want %s", idx.Version, types.INDEX_VERSION)
41 }
42
43 if idx.Origin != "https://plc.directory" {
44 t.Errorf("origin mismatch: got %s", idx.Origin)
45 }
46
47 if idx.Count() != 0 {
48 t.Error("new index should be empty")
49 }
50 })
51
52 t.Run("NewIndex_EmptyOrigin", func(t *testing.T) {
53 idx := bundleindex.NewIndex("")
54
55 if idx.Origin != "" {
56 t.Error("should allow empty origin")
57 }
58 })
59}
60
61func TestIndexAddBundle(t *testing.T) {
62 t.Run("AddSingleBundle", func(t *testing.T) {
63 idx := bundleindex.NewIndex("test-origin")
64
65 meta := &bundleindex.BundleMetadata{
66 BundleNumber: 1,
67 StartTime: time.Now(),
68 EndTime: time.Now().Add(time.Hour),
69 OperationCount: types.BUNDLE_SIZE,
70 DIDCount: 1000,
71 Hash: "hash123",
72 ContentHash: "content123",
73 CompressedHash: "compressed123",
74 CompressedSize: 1024,
75 UncompressedSize: 5120,
76 }
77
78 idx.AddBundle(meta)
79
80 if idx.Count() != 1 {
81 t.Errorf("count should be 1, got %d", idx.Count())
82 }
83
84 retrieved, err := idx.GetBundle(1)
85 if err != nil {
86 t.Fatalf("GetBundle failed: %v", err)
87 }
88
89 if retrieved.Hash != "hash123" {
90 t.Error("hash mismatch after retrieval")
91 }
92 })
93
94 t.Run("AddMultipleBundles_AutoSort", func(t *testing.T) {
95 idx := bundleindex.NewIndex("test-origin")
96
97 // Add bundles out of order: 3, 1, 2
98 for _, num := range []int{3, 1, 2} {
99 meta := &bundleindex.BundleMetadata{
100 BundleNumber: num,
101 StartTime: time.Now(),
102 EndTime: time.Now().Add(time.Hour),
103 OperationCount: types.BUNDLE_SIZE,
104 }
105 idx.AddBundle(meta)
106 }
107
108 bundles := idx.GetBundles()
109
110 // Should be sorted: 1, 2, 3
111 if bundles[0].BundleNumber != 1 {
112 t.Error("bundles not sorted")
113 }
114 if bundles[1].BundleNumber != 2 {
115 t.Error("bundles not sorted")
116 }
117 if bundles[2].BundleNumber != 3 {
118 t.Error("bundles not sorted")
119 }
120 })
121
122 t.Run("UpdateExistingBundle", func(t *testing.T) {
123 idx := bundleindex.NewIndex("test-origin")
124
125 original := &bundleindex.BundleMetadata{
126 BundleNumber: 1,
127 Hash: "original_hash",
128 StartTime: time.Now(),
129 EndTime: time.Now().Add(time.Hour),
130 OperationCount: types.BUNDLE_SIZE,
131 }
132
133 idx.AddBundle(original)
134
135 // Add again with different hash (update)
136 updated := &bundleindex.BundleMetadata{
137 BundleNumber: 1,
138 Hash: "updated_hash",
139 StartTime: time.Now(),
140 EndTime: time.Now().Add(time.Hour),
141 OperationCount: types.BUNDLE_SIZE,
142 }
143
144 idx.AddBundle(updated)
145
146 // Should have only 1 bundle (updated, not duplicated)
147 if idx.Count() != 1 {
148 t.Errorf("should have 1 bundle after update, got %d", idx.Count())
149 }
150
151 retrieved, _ := idx.GetBundle(1)
152 if retrieved.Hash != "updated_hash" {
153 t.Error("bundle was not updated")
154 }
155 })
156}
157
158// ====================================================================================
159// SAVE & LOAD TESTS
160// ====================================================================================
161
162func TestIndexPersistence(t *testing.T) {
163 tmpDir := t.TempDir()
164
165 t.Run("SaveAndLoad", func(t *testing.T) {
166 indexPath := filepath.Join(tmpDir, "test_index.json")
167
168 // Create and populate index
169 idx := bundleindex.NewIndex("https://plc.directory")
170
171 for i := 1; i <= 5; i++ {
172 meta := &bundleindex.BundleMetadata{
173 BundleNumber: i,
174 StartTime: time.Now().Add(time.Duration(i-1) * time.Hour),
175 EndTime: time.Now().Add(time.Duration(i) * time.Hour),
176 OperationCount: types.BUNDLE_SIZE,
177 DIDCount: 1000 * i,
178 Hash: fmt.Sprintf("hash%d", i),
179 ContentHash: fmt.Sprintf("content%d", i),
180 CompressedHash: fmt.Sprintf("compressed%d", i),
181 CompressedSize: int64(1024 * i),
182 UncompressedSize: int64(5120 * i),
183 }
184 idx.AddBundle(meta)
185 }
186
187 // Save
188 if err := idx.Save(indexPath); err != nil {
189 t.Fatalf("Save failed: %v", err)
190 }
191
192 // Verify file exists
193 if _, err := os.Stat(indexPath); os.IsNotExist(err) {
194 t.Fatal("index file not created")
195 }
196
197 // Load
198 loaded, err := bundleindex.LoadIndex(indexPath)
199 if err != nil {
200 t.Fatalf("LoadIndex failed: %v", err)
201 }
202
203 // Verify data integrity
204 if loaded.Count() != 5 {
205 t.Errorf("loaded count mismatch: got %d, want 5", loaded.Count())
206 }
207
208 if loaded.Origin != "https://plc.directory" {
209 t.Error("origin not preserved")
210 }
211
212 if loaded.LastBundle != 5 {
213 t.Error("LastBundle not calculated correctly")
214 }
215
216 // Verify specific bundle
217 bundle3, err := loaded.GetBundle(3)
218 if err != nil {
219 t.Fatalf("GetBundle(3) failed: %v", err)
220 }
221
222 if bundle3.Hash != "hash3" {
223 t.Error("bundle data not preserved")
224 }
225 })
226
227 t.Run("AtomicSave", func(t *testing.T) {
228 indexPath := filepath.Join(tmpDir, "atomic_test.json")
229
230 idx := bundleindex.NewIndex("test")
231 idx.AddBundle(&bundleindex.BundleMetadata{
232 BundleNumber: 1,
233 StartTime: time.Now(),
234 EndTime: time.Now(),
235 OperationCount: types.BUNDLE_SIZE,
236 })
237
238 idx.Save(indexPath)
239
240 // Verify no .tmp file left behind
241 tmpPath := indexPath + ".tmp"
242 if _, err := os.Stat(tmpPath); !os.IsNotExist(err) {
243 t.Error("temporary file should not exist after successful save")
244 }
245 })
246
247 t.Run("LoadInvalidVersion", func(t *testing.T) {
248 indexPath := filepath.Join(tmpDir, "invalid_version.json")
249
250 // Write index with wrong version
251 invalidData := `{"version":"99.99","origin":"test","bundles":[]}`
252 os.WriteFile(indexPath, []byte(invalidData), 0644)
253
254 _, err := bundleindex.LoadIndex(indexPath)
255 if err == nil {
256 t.Error("should reject index with invalid version")
257 }
258 })
259
260 t.Run("LoadCorruptedJSON", func(t *testing.T) {
261 indexPath := filepath.Join(tmpDir, "corrupted.json")
262
263 os.WriteFile(indexPath, []byte("{invalid json"), 0644)
264
265 _, err := bundleindex.LoadIndex(indexPath)
266 if err == nil {
267 t.Error("should reject corrupted JSON")
268 }
269 })
270}
271
272// ====================================================================================
273// QUERY OPERATIONS
274// ====================================================================================
275
276func TestIndexQueries(t *testing.T) {
277 idx := bundleindex.NewIndex("test")
278
279 // Populate with bundles
280 for i := 1; i <= 10; i++ {
281 meta := &bundleindex.BundleMetadata{
282 BundleNumber: i,
283 StartTime: time.Now().Add(time.Duration(i-1) * time.Hour),
284 EndTime: time.Now().Add(time.Duration(i) * time.Hour),
285 OperationCount: types.BUNDLE_SIZE,
286 CompressedSize: int64(i * 1000),
287 }
288 idx.AddBundle(meta)
289 }
290
291 t.Run("GetBundle", func(t *testing.T) {
292 meta, err := idx.GetBundle(5)
293 if err != nil {
294 t.Fatalf("GetBundle failed: %v", err)
295 }
296
297 if meta.BundleNumber != 5 {
298 t.Error("wrong bundle returned")
299 }
300 })
301
302 t.Run("GetBundle_NotFound", func(t *testing.T) {
303 _, err := idx.GetBundle(999)
304 if err == nil {
305 t.Error("should return error for nonexistent bundle")
306 }
307 })
308
309 t.Run("GetLastBundle", func(t *testing.T) {
310 last := idx.GetLastBundle()
311
312 if last == nil {
313 t.Fatal("GetLastBundle returned nil")
314 }
315
316 if last.BundleNumber != 10 {
317 t.Errorf("last bundle should be 10, got %d", last.BundleNumber)
318 }
319 })
320
321 t.Run("GetLastBundle_Empty", func(t *testing.T) {
322 emptyIdx := bundleindex.NewIndex("test")
323
324 last := emptyIdx.GetLastBundle()
325
326 if last != nil {
327 t.Error("empty index should return nil for GetLastBundle")
328 }
329 })
330
331 t.Run("GetBundleRange", func(t *testing.T) {
332 bundles := idx.GetBundleRange(3, 7)
333
334 if len(bundles) != 5 {
335 t.Errorf("expected 5 bundles, got %d", len(bundles))
336 }
337
338 if bundles[0].BundleNumber != 3 || bundles[4].BundleNumber != 7 {
339 t.Error("range boundaries incorrect")
340 }
341 })
342
343 t.Run("GetBundleRange_OutOfBounds", func(t *testing.T) {
344 bundles := idx.GetBundleRange(100, 200)
345
346 if len(bundles) != 0 {
347 t.Errorf("expected 0 bundles for out-of-range query, got %d", len(bundles))
348 }
349 })
350
351 t.Run("GetBundles_ReturnsShallowCopy", func(t *testing.T) {
352 bundles1 := idx.GetBundles()
353 bundles2 := idx.GetBundles()
354
355 // Should be different slices
356 if &bundles1[0] == &bundles2[0] {
357 t.Error("GetBundles should return copy, not same slice")
358 }
359
360 // But same data
361 if bundles1[0].BundleNumber != bundles2[0].BundleNumber {
362 t.Error("bundle data should be same")
363 }
364 })
365}
366
367// ====================================================================================
368// GAP DETECTION - CRITICAL FOR INTEGRITY
369// ====================================================================================
370
371func TestIndexFindGaps(t *testing.T) {
372 t.Run("NoGaps", func(t *testing.T) {
373 idx := bundleindex.NewIndex("test")
374
375 for i := 1; i <= 10; i++ {
376 idx.AddBundle(createTestMetadata(i))
377 }
378
379 gaps := idx.FindGaps()
380
381 if len(gaps) != 0 {
382 t.Errorf("expected no gaps, found %d: %v", len(gaps), gaps)
383 }
384 })
385
386 t.Run("SingleGap", func(t *testing.T) {
387 idx := bundleindex.NewIndex("test")
388
389 // Add bundles 1, 2, 4, 5 (missing 3)
390 for _, num := range []int{1, 2, 4, 5} {
391 idx.AddBundle(createTestMetadata(num))
392 }
393
394 gaps := idx.FindGaps()
395
396 if len(gaps) != 1 {
397 t.Errorf("expected 1 gap, got %d", len(gaps))
398 }
399
400 if len(gaps) > 0 && gaps[0] != 3 {
401 t.Errorf("expected gap at 3, got %d", gaps[0])
402 }
403 })
404
405 t.Run("MultipleGaps", func(t *testing.T) {
406 idx := bundleindex.NewIndex("test")
407
408 // Add bundles 1, 2, 5, 6, 9, 10 (missing 3, 4, 7, 8)
409 for _, num := range []int{1, 2, 5, 6, 9, 10} {
410 idx.AddBundle(createTestMetadata(num))
411 }
412
413 gaps := idx.FindGaps()
414
415 expectedGaps := []int{3, 4, 7, 8}
416 if len(gaps) != len(expectedGaps) {
417 t.Errorf("expected %d gaps, got %d", len(expectedGaps), len(gaps))
418 }
419
420 for i, expected := range expectedGaps {
421 if gaps[i] != expected {
422 t.Errorf("gap %d: got %d, want %d", i, gaps[i], expected)
423 }
424 }
425 })
426
427 t.Run("FindGaps_EmptyIndex", func(t *testing.T) {
428 idx := bundleindex.NewIndex("test")
429
430 gaps := idx.FindGaps()
431
432 if len(gaps) > 0 {
433 t.Error("empty index should have no gaps")
434 }
435 })
436
437 t.Run("FindGaps_NonSequentialStart", func(t *testing.T) {
438 idx := bundleindex.NewIndex("test")
439
440 // Start at bundle 100
441 for i := 100; i <= 105; i++ {
442 idx.AddBundle(createTestMetadata(i))
443 }
444
445 gaps := idx.FindGaps()
446
447 // No gaps between 100-105
448 if len(gaps) != 0 {
449 t.Errorf("expected no gaps, got %d", len(gaps))
450 }
451 })
452}
453
454// ====================================================================================
455// STATISTICS & DERIVED FIELDS
456// ====================================================================================
457
458func TestIndexStatistics(t *testing.T) {
459 idx := bundleindex.NewIndex("test")
460
461 t.Run("StatsEmpty", func(t *testing.T) {
462 stats := idx.GetStats()
463
464 if stats["bundle_count"].(int) != 0 {
465 t.Error("empty index should have count 0")
466 }
467 })
468
469 t.Run("StatsPopulated", func(t *testing.T) {
470 totalSize := int64(0)
471 totalUncompressed := int64(0)
472
473 for i := 1; i <= 5; i++ {
474 meta := &bundleindex.BundleMetadata{
475 BundleNumber: i,
476 StartTime: time.Now().Add(time.Duration(i-1) * time.Hour),
477 EndTime: time.Now().Add(time.Duration(i) * time.Hour),
478 OperationCount: types.BUNDLE_SIZE,
479 CompressedSize: int64(1000 * i),
480 UncompressedSize: int64(5000 * i),
481 }
482 idx.AddBundle(meta)
483 totalSize += meta.CompressedSize
484 totalUncompressed += meta.UncompressedSize
485 }
486
487 stats := idx.GetStats()
488
489 if stats["bundle_count"].(int) != 5 {
490 t.Error("bundle count mismatch")
491 }
492
493 if stats["first_bundle"].(int) != 1 {
494 t.Error("first_bundle mismatch")
495 }
496
497 if stats["last_bundle"].(int) != 5 {
498 t.Error("last_bundle mismatch")
499 }
500
501 if stats["total_size"].(int64) != totalSize {
502 t.Errorf("total_size mismatch: got %d, want %d", stats["total_size"].(int64), totalSize)
503 }
504
505 if stats["total_uncompressed_size"].(int64) != totalUncompressed {
506 t.Error("total_uncompressed_size mismatch")
507 }
508
509 if _, ok := stats["start_time"]; !ok {
510 t.Error("stats missing start_time")
511 }
512
513 if _, ok := stats["end_time"]; !ok {
514 t.Error("stats missing end_time")
515 }
516
517 if stats["gaps"].(int) != 0 {
518 t.Error("should have no gaps")
519 }
520 })
521
522 t.Run("StatsRecalculateAfterAdd", func(t *testing.T) {
523 idx := bundleindex.NewIndex("test")
524
525 idx.AddBundle(&bundleindex.BundleMetadata{
526 BundleNumber: 1,
527 StartTime: time.Now(),
528 EndTime: time.Now(),
529 OperationCount: types.BUNDLE_SIZE,
530 CompressedSize: 1000,
531 })
532
533 stats1 := idx.GetStats()
534 size1 := stats1["total_size"].(int64)
535
536 // Add another bundle
537 idx.AddBundle(&bundleindex.BundleMetadata{
538 BundleNumber: 2,
539 StartTime: time.Now(),
540 EndTime: time.Now(),
541 OperationCount: types.BUNDLE_SIZE,
542 CompressedSize: 2000,
543 })
544
545 stats2 := idx.GetStats()
546 size2 := stats2["total_size"].(int64)
547
548 if size2 != size1+2000 {
549 t.Errorf("total_size not recalculated: got %d, want %d", size2, size1+2000)
550 }
551
552 if stats2["last_bundle"].(int) != 2 {
553 t.Error("last_bundle not recalculated")
554 }
555 })
556}
557
558// ====================================================================================
559// REBUILD OPERATION
560// ====================================================================================
561
562func TestIndexRebuild(t *testing.T) {
563 t.Run("RebuildFromMetadata", func(t *testing.T) {
564 idx := bundleindex.NewIndex("original")
565
566 // Add some bundles
567 for i := 1; i <= 3; i++ {
568 idx.AddBundle(createTestMetadata(i))
569 }
570
571 if idx.Count() != 3 {
572 t.Fatal("setup failed")
573 }
574
575 // Create new metadata for rebuild
576 newMetadata := []*bundleindex.BundleMetadata{
577 createTestMetadata(1),
578 createTestMetadata(2),
579 createTestMetadata(5),
580 createTestMetadata(6),
581 }
582
583 // Rebuild
584 idx.Rebuild(newMetadata)
585
586 // Should now have 4 bundles
587 if idx.Count() != 4 {
588 t.Errorf("after rebuild, expected 4 bundles, got %d", idx.Count())
589 }
590
591 // Should have new bundles 5, 6
592 if _, err := idx.GetBundle(5); err != nil {
593 t.Error("should have bundle 5 after rebuild")
594 }
595
596 // Should not have bundle 3
597 if _, err := idx.GetBundle(3); err == nil {
598 t.Error("should not have bundle 3 after rebuild")
599 }
600
601 // Origin should be preserved
602 if idx.Origin != "original" {
603 t.Error("origin should be preserved during rebuild")
604 }
605 })
606
607 t.Run("RebuildAutoSorts", func(t *testing.T) {
608 idx := bundleindex.NewIndex("test")
609
610 // Rebuild with unsorted data
611 unsorted := []*bundleindex.BundleMetadata{
612 createTestMetadata(5),
613 createTestMetadata(2),
614 createTestMetadata(8),
615 createTestMetadata(1),
616 }
617
618 idx.Rebuild(unsorted)
619
620 bundles := idx.GetBundles()
621
622 // Should be sorted
623 for i := 0; i < len(bundles)-1; i++ {
624 if bundles[i].BundleNumber >= bundles[i+1].BundleNumber {
625 t.Error("bundles not sorted after rebuild")
626 }
627 }
628 })
629}
630
631// ====================================================================================
632// CLEAR OPERATION
633// ====================================================================================
634
635func TestIndexClear(t *testing.T) {
636 idx := bundleindex.NewIndex("test")
637
638 // Populate
639 for i := 1; i <= 10; i++ {
640 idx.AddBundle(createTestMetadata(i))
641 }
642
643 if idx.Count() != 10 {
644 t.Fatal("setup failed")
645 }
646
647 // Clear
648 idx.Clear()
649
650 if idx.Count() != 0 {
651 t.Error("count should be 0 after clear")
652 }
653
654 if idx.LastBundle != 0 {
655 t.Error("LastBundle should be 0 after clear")
656 }
657
658 if idx.TotalSize != 0 {
659 t.Error("TotalSize should be 0 after clear")
660 }
661
662 // Should be able to add after clear
663 idx.AddBundle(createTestMetadata(1))
664
665 if idx.Count() != 1 {
666 t.Error("should be able to add after clear")
667 }
668}
669
670// ====================================================================================
671// CONCURRENCY TESTS
672// ====================================================================================
673
674func TestIndexConcurrency(t *testing.T) {
675 t.Run("ConcurrentReads", func(t *testing.T) {
676 idx := bundleindex.NewIndex("test")
677
678 // Populate
679 for i := 1; i <= 100; i++ {
680 idx.AddBundle(createTestMetadata(i))
681 }
682
683 // 100 concurrent readers
684 var wg sync.WaitGroup
685 errors := make(chan error, 100)
686
687 for i := 0; i < 100; i++ {
688 wg.Add(1)
689 go func(id int) {
690 defer wg.Done()
691
692 // Various read operations
693 idx.Count()
694 idx.GetLastBundle()
695 idx.GetBundles()
696 idx.FindGaps()
697 idx.GetStats()
698
699 if _, err := idx.GetBundle(id%100 + 1); err != nil {
700 errors <- err
701 }
702 }(i)
703 }
704
705 wg.Wait()
706 close(errors)
707
708 for err := range errors {
709 t.Errorf("concurrent read error: %v", err)
710 }
711 })
712
713 t.Run("ConcurrentReadsDuringSave", func(t *testing.T) {
714 tmpDir := t.TempDir()
715 indexPath := filepath.Join(tmpDir, "concurrent.json")
716
717 idx := bundleindex.NewIndex("test")
718
719 for i := 1; i <= 50; i++ {
720 idx.AddBundle(createTestMetadata(i))
721 }
722
723 var wg sync.WaitGroup
724
725 // Saver goroutine
726 wg.Add(1)
727 go func() {
728 defer wg.Done()
729 for i := 0; i < 10; i++ {
730 idx.Save(indexPath)
731 time.Sleep(10 * time.Millisecond)
732 }
733 }()
734
735 // Reader goroutines
736 for i := 0; i < 10; i++ {
737 wg.Add(1)
738 go func() {
739 defer wg.Done()
740 for j := 0; j < 50; j++ {
741 idx.Count()
742 idx.GetBundles()
743 time.Sleep(5 * time.Millisecond)
744 }
745 }()
746 }
747
748 wg.Wait()
749 })
750}
751
752// ====================================================================================
753// REMOTE UPDATE TESTS (FOR CLONING)
754// ====================================================================================
755
756func TestIndexUpdateFromRemote(t *testing.T) {
757
758 t.Run("UpdateFromRemote_Basic", func(t *testing.T) {
759 idx := bundleindex.NewIndex("test")
760
761 // Local has bundles 1-3
762 for i := 1; i <= 3; i++ {
763 idx.AddBundle(createTestMetadata(i))
764 }
765
766 // Remote has bundles 1-5
767 remoteMeta := make(map[int]*bundleindex.BundleMetadata)
768 for i := 1; i <= 5; i++ {
769 remoteMeta[i] = createTestMetadata(i)
770 }
771
772 bundlesToUpdate := []int{4, 5}
773
774 // Mock file existence (4 and 5 exist)
775 fileExists := func(bundleNum int) bool {
776 return bundleNum == 4 || bundleNum == 5
777 }
778
779 logger := &testLogger{t: &testing.T{}}
780
781 err := idx.UpdateFromRemote(bundlesToUpdate, remoteMeta, fileExists, false, logger)
782 if err != nil {
783 t.Fatalf("UpdateFromRemote failed: %v", err)
784 }
785
786 // Should now have 5 bundles
787 if idx.Count() != 5 {
788 t.Errorf("expected 5 bundles after update, got %d", idx.Count())
789 }
790 })
791
792 t.Run("UpdateFromRemote_SkipsMissingFiles", func(t *testing.T) {
793 idx := bundleindex.NewIndex("test")
794
795 remoteMeta := map[int]*bundleindex.BundleMetadata{
796 1: createTestMetadata(1),
797 2: createTestMetadata(2),
798 }
799
800 bundlesToUpdate := []int{1, 2}
801
802 // Only bundle 1 exists locally
803 fileExists := func(bundleNum int) bool {
804 return bundleNum == 1
805 }
806
807 logger := &testLogger{t: &testing.T{}}
808
809 err := idx.UpdateFromRemote(bundlesToUpdate, remoteMeta, fileExists, false, logger)
810 if err != nil {
811 t.Fatalf("UpdateFromRemote failed: %v", err)
812 }
813
814 // Should only have bundle 1
815 if idx.Count() != 1 {
816 t.Errorf("expected 1 bundle, got %d", idx.Count())
817 }
818
819 if _, err := idx.GetBundle(2); err == nil {
820 t.Error("should not have bundle 2 (file missing)")
821 }
822 })
823}
824
825// ====================================================================================
826// HELPER FUNCTIONS
827// ====================================================================================
828
829func createTestMetadata(bundleNum int) *bundleindex.BundleMetadata {
830 return &bundleindex.BundleMetadata{
831 BundleNumber: bundleNum,
832 StartTime: time.Now().Add(time.Duration(bundleNum-1) * time.Hour),
833 EndTime: time.Now().Add(time.Duration(bundleNum) * time.Hour),
834 OperationCount: types.BUNDLE_SIZE,
835 DIDCount: 1000,
836 Hash: fmt.Sprintf("hash%d", bundleNum),
837 ContentHash: fmt.Sprintf("content%d", bundleNum),
838 Parent: fmt.Sprintf("parent%d", bundleNum-1),
839 CompressedHash: fmt.Sprintf("compressed%d", bundleNum),
840 CompressedSize: int64(1000 * bundleNum),
841 UncompressedSize: int64(5000 * bundleNum),
842 CreatedAt: time.Now(),
843 }
844}