/** * AriaEngine — OPFSBackend v2 测试 * * 覆盖: * 1. read/write/append/exists/delete/clear 基本语义 * 2. append 追加语义(keepExistingData + position)与字节顺序 * 3. 写队列串行化(并发写顺序一致) * 4. 单次任务失败不中断队列链(后续写正常) * 5. close 等待挂起写完成 * 6. open 清理崩溃残留临时文件(.crswap/.tmp) * 7. writeMany/deleteMany 语义 */ // =================================================================== // v0.8.0: 删除本文件内的第三份 OPFS mock —— 改用共享的 storage-harness。 // // 原实现的问题(见 v0.8.0 迭代工作流 C-1): // - 与 tests/helpers/opfs-mock.ts 重复(两份都在测同一件事,语义各自漂移); // - `close()` 是空函数、写入立即可见 → 无法表达"提交前崩溃"; // - 定义了 `writeCalls` 记录但**从未被任何断言使用**(死代码); // - `entry.content.subarray ? entry.content : entry.content` 是恒等表达式(无意义分支)。 // // 共享 harness 提供:真实提交语义(close 才可见)、读返回副本、 // keepExistingData:false 截断、字节级故障注入、真崩溃模拟。 // =================================================================== import { OPFSBackend } from '../../src/engine/aria/store/opfs_backend'; import { resetOPFSMock } from '../helpers/storage-harness'; import { decode as decodeBytes } from '../helpers/assertions'; const enc = (s: string) => new Uint8Array(new TextEncoder().encode(s)).buffer; describe('AriaEngine — OPFSBackend v2', () => { beforeEach(() => { resetOPFSMock(); }); it('read/write/exists/delete 基本语义', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-1'); expect(backend.isOpen()).toBe(true); await backend.write('k1', enc('hello')); expect(await backend.exists('k1')).toBe(true); expect(decodeBytes(await backend.read('k1'))).toBe('hello'); expect(await backend.read('missing')).toBeNull(); await backend.delete('k1'); expect(await backend.exists('k1')).toBe(false); await backend.close(); expect(backend.isOpen()).toBe(false); }); it('append 追加语义:字节顺序与位置正确', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-2'); await backend.append('wal', enc('AAA')); await backend.append('wal', enc('BBB')); await backend.append('wal', enc('CCC')); const all = await backend.read('wal'); expect(decodeBytes(all)).toBe('AAABBBCCC'); await backend.close(); }); it('write 覆盖语义(COW 原子替换)', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-3'); await backend.write('f', enc('OLD-CONTENT')); await backend.write('f', enc('NEW')); expect(decodeBytes(await backend.read('f'))).toBe('NEW'); await backend.close(); }); it('并发写串行化:写入顺序与调用顺序一致', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-4'); // 同时发起多个写(不 await 中间结果) const p1 = backend.append('log', enc('A')); const p2 = backend.append('log', enc('B')); const p3 = backend.append('log', enc('C')); await Promise.all([p1, p2, p3]); expect(decodeBytes(await backend.read('log'))).toBe('ABC'); await backend.close(); }); it('单次写失败不中断队列链,后续写正常', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-5'); // 注入失败:让 write 抛错一次 let failNext = true; const origWrite = backend.write.bind(backend); const mockDir = (backend as any).dbDir; const origGetFileHandle = mockDir.getFileHandle.bind(mockDir); mockDir.getFileHandle = async (name: string, opts?: { create?: boolean }) => { if (failNext && name === 'boom') { failNext = false; throw new Error('Injected write failure'); } return origGetFileHandle(name, opts); }; (backend as any).dbDir = mockDir; await expect(origWrite('boom', enc('x'))).rejects.toThrow('Injected write failure'); // 队列链恢复:后续写成功 await backend.write('ok', enc('fine')); expect(decodeBytes(await backend.read('ok'))).toBe('fine'); await backend.close(); }); it('close 等待挂起写完成(挂起写不丢失)', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-6'); // 发起一个写但不 await,立即 close const pending = backend.append('late', enc('LATE-DATA')); await backend.close(); await pending; // close 已等队列 → 写已完成 expect(pending).resolves.toBeUndefined(); }); it('open 清理崩溃残留临时文件(.crswap/.tmp)', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-7'); await backend.write('data', enc('real')); // 手工制造残留 const mockDir = (backend as any).dbDir; await mockDir.getFileHandle('data.crswap', { create: true }); await mockDir.getFileHandle('data.tmp', { create: true }); await backend.close(); // 重开:open 自动清理 const backend2 = new OPFSBackend(); await backend2.open('opfs-test-7'); const keys = await backend2.listKeys(); expect(keys).toEqual(['data']); expect(keys.some((k) => k.endsWith('.crswap') || k.endsWith('.tmp'))).toBe(false); // 正常数据不受影响 expect(decodeBytes(await backend2.read('data'))).toBe('real'); await backend2.close(); }); it('writeMany / deleteMany 语义', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-8'); await backend.writeMany({ a: enc('AAA'), b: enc('BBB') }); expect(decodeBytes(await backend.read('a'))).toBe('AAA'); expect(decodeBytes(await backend.read('b'))).toBe('BBB'); await backend.deleteMany(['a']); expect(await backend.exists('a')).toBe(false); expect(await backend.exists('b')).toBe(true); await backend.clear(); expect(await backend.listKeys()).toEqual([]); await backend.close(); }); it('close 后所有操作安全返回(dbDir 为 null 不抛错)', async () => { const backend = new OPFSBackend(); await backend.open('opfs-test-9'); await backend.write('k', enc('v')); await backend.close(); expect(backend.isOpen()).toBe(false); expect(await backend.read('k')).toBeNull(); await backend.write('late', enc('x')); // 静默忽略 await backend.append('late2', enc('x')); await backend.writeMany({ a: enc('1') }); await backend.delete('k'); await backend.deleteMany(['a']); expect(await backend.listKeys()).toEqual([]); expect(await backend.exists('k')).toBe(false); await backend.clear(); await backend.cleanupStaleFiles(); }); });