⚡ Zero-dependency plcbundle library exclusively for Bun
at main 4.9 kB view raw
1import { describe, test, expect, beforeEach } from 'bun:test'; 2import { PLCBundle } from '../src/plcbundle'; 3import { TEMP_DIR, createMockIndex } from './setup'; 4 5describe('PLCBundle', () => { 6 let bundle: PLCBundle; 7 8 beforeEach(() => { 9 bundle = new PLCBundle(TEMP_DIR); 10 }); 11 12 describe('constructor', () => { 13 test('initializes with default directory', () => { 14 const b = new PLCBundle(); 15 expect(b).toBeDefined(); 16 }); 17 18 test('normalizes directory path', () => { 19 const b1 = new PLCBundle('/test/path'); 20 const b2 = new PLCBundle('/test/path/'); 21 expect(b1.getBundlePath(1)).toBe(b2.getBundlePath(1)); 22 }); 23 24 test('accepts custom index path', () => { 25 const b = new PLCBundle('./', './custom-index.json'); 26 expect(b).toBeDefined(); 27 }); 28 }); 29 30 describe('saveIndex', () => { 31 test('saves index to file', async () => { 32 const mockIndex = createMockIndex(); 33 await bundle.saveIndex(mockIndex); 34 35 const file = Bun.file(`${TEMP_DIR}/plc_bundles.json`); 36 expect(await file.exists()).toBe(true); 37 38 const saved = await file.json(); 39 expect(saved.version).toBe('1.0'); 40 expect(saved.bundles.length).toBe(3); 41 }); 42 43 test('updates cached index', async () => { 44 const mockIndex = createMockIndex(); 45 await bundle.saveIndex(mockIndex); 46 47 const loaded = await bundle.loadIndex(); 48 expect(loaded.last_bundle).toBe(mockIndex.last_bundle); 49 }); 50 }); 51 52 describe('loadIndex', () => { 53 test('loads index from file', async () => { 54 const mockIndex = createMockIndex(); 55 await bundle.saveIndex(mockIndex); 56 57 const loaded = await bundle.loadIndex(true); 58 expect(loaded.version).toBe('1.0'); 59 expect(loaded.bundles.length).toBe(3); 60 }); 61 62 test('returns cached index', async () => { 63 const mockIndex = createMockIndex(); 64 await bundle.saveIndex(mockIndex); 65 66 const first = await bundle.loadIndex(); 67 const second = await bundle.loadIndex(); 68 expect(first).toBe(second); // Same reference 69 }); 70 71 test('refreshes cache when requested', async () => { 72 const mockIndex = createMockIndex(); 73 await bundle.saveIndex(mockIndex); 74 75 const first = await bundle.loadIndex(); 76 const second = await bundle.loadIndex(true); 77 expect(first).not.toBe(second); // Different reference 78 }); 79 }); 80 81 describe('getMetadata', () => { 82 test('returns metadata for existing bundle', async () => { 83 const mockIndex = createMockIndex(); 84 await bundle.saveIndex(mockIndex); 85 86 const metadata = await bundle.getMetadata(2); 87 expect(metadata).toBeDefined(); 88 expect(metadata?.bundle_number).toBe(2); 89 expect(metadata?.operation_count).toBe(10000); 90 }); 91 92 test('returns undefined for non-existent bundle', async () => { 93 const mockIndex = createMockIndex(); 94 await bundle.saveIndex(mockIndex); 95 96 const metadata = await bundle.getMetadata(999); 97 expect(metadata).toBeUndefined(); 98 }); 99 }); 100 101 describe('getBundlePath', () => { 102 test('generates correct path with padding', () => { 103 const path1 = bundle.getBundlePath(1); 104 const path42 = bundle.getBundlePath(42); 105 const path1000 = bundle.getBundlePath(1000); 106 107 expect(path1).toContain('000001.jsonl.zst'); 108 expect(path42).toContain('000042.jsonl.zst'); 109 expect(path1000).toContain('001000.jsonl.zst'); 110 }); 111 }); 112 113 describe('getStats', () => { 114 test('returns repository statistics', async () => { 115 const mockIndex = createMockIndex(); 116 await bundle.saveIndex(mockIndex); 117 118 const stats = await bundle.getStats(); 119 expect(stats.version).toBe('1.0'); 120 expect(stats.lastBundle).toBe(3); 121 expect(stats.totalBundles).toBe(3); 122 expect(stats.totalSize).toBe(5000000); 123 }); 124 }); 125 126 describe('calculateChainHash', () => { 127 test('calculates genesis hash', () => { 128 const hash = bundle.calculateChainHash('', 'content123', true); 129 expect(hash).toBeDefined(); 130 expect(hash.length).toBe(64); // SHA-256 hex length 131 }); 132 133 test('calculates chain hash', () => { 134 const hash = bundle.calculateChainHash('parent123', 'content456', false); 135 expect(hash).toBeDefined(); 136 expect(hash.length).toBe(64); 137 }); 138 139 test('produces different hashes for different inputs', () => { 140 const hash1 = bundle.calculateChainHash('parent1', 'content1', false); 141 const hash2 = bundle.calculateChainHash('parent2', 'content2', false); 142 expect(hash1).not.toBe(hash2); 143 }); 144 145 test('produces same hash for same inputs', () => { 146 const hash1 = bundle.calculateChainHash('parent', 'content', false); 147 const hash2 = bundle.calculateChainHash('parent', 'content', false); 148 expect(hash1).toBe(hash2); 149 }); 150 }); 151});