/** * 故障注入基座自测(v0.8.0 工作流 C-1) * * 这些测试的作用不是测产品代码,而是**证明测试基座本身是有效的**: * 如果故障注入不能稳定复现"崩溃丢数据",那么后面所有崩溃相关测试都是空头支票。 */ import { TransactionalFileStore, installOPFSMock, clearRegistry, CrashableStoreBackend } from './storage-harness'; import { FaultyBackend } from './faulty-backend'; import { OPFSBackend } from '../../src/engine/aria/store/opfs_backend'; import { decode as decodeBytes } from './assertions'; describe('[v0.8.0] 测试基座:TransactionalFileStore', () => { let store: TransactionalFileStore; beforeEach(() => { clearRegistry(); store = new TransactionalFileStore({ dbName: 'unit' }); }); test('提交语义:未提交的写入对读不可见(真实 OPFS 是 close 才原子替换)', async () => { await store.write('k', new TextEncoder().encode('v1').buffer as ArrayBuffer); expect(await store.read('k')).toBeNull(); // 未 commit → 读不到 store.commitAll(); expect(decodeBytes(await store.read('k'))).toBe('v1'); }); test('崩溃语义:丢弃未提交写入,已提交内容保持', async () => { await store.write('k', new TextEncoder().encode('committed').buffer as ArrayBuffer); store.commitAll(); await store.write('k', new TextEncoder().encode('pending').buffer as ArrayBuffer); expect(store.hasPending()).toBe(true); store.crashPending(); // 崩溃:丢掉 pending expect(decodeBytes(await store.read('k'))).toBe('committed'); expect(store.hasPending()).toBe(false); }); test('读返回副本:调用方原地修改不会污染"磁盘"(旧 mock 的缺陷 1)', async () => { await store.write('k', new TextEncoder().encode('original').buffer as ArrayBuffer); store.commitAll(); const first = await store.read('k'); new Uint8Array(first!)[0] = 0x58; // 'X' const second = await store.read('k'); expect(decodeBytes(second)).toBe('original'); }); test('故障注入:failNextWrite / failNextAppend / failNextDelete 精确计数', async () => { store.failNextWrite(); await expect(store.write('k', new ArrayBuffer(4))).rejects.toThrow(/injected write failure/); await expect(store.write('k', new ArrayBuffer(4))).resolves.toBeUndefined(); store.failNextAppend(2); await expect(store.append('k', new ArrayBuffer(2))).rejects.toThrow(/injected append failure/); await expect(store.append('k', new ArrayBuffer(2))).rejects.toThrow(/injected append failure/); await expect(store.append('k', new ArrayBuffer(2))).resolves.toBeUndefined(); store.failNextDelete(); await expect(store.delete('k')).rejects.toThrow(/injected delete failure/); }); test('撕裂写:truncateNextAppendTo 只落前 n 字节', async () => { const text = new TextEncoder(); await store.write('log', text.encode('AAAA').buffer as ArrayBuffer); store.commitAll(); store.truncateNextAppendTo(2); await store.append('log', text.encode('BBBB').buffer as ArrayBuffer); store.commitAll(); // 已提交的 'AAAA' 保留 + 撕裂后只追加了 'BB' → 'AAAABB' expect(decodeBytes(await store.read('log'))).toBe('AAAABB'); }); test('篡改已提交字节:corruptCommitted 用于 bit-flip 场景', async () => { await store.write('k', new TextEncoder().encode('hello').buffer as ArrayBuffer); store.commitAll(); const ok = store.corruptCommitted('k', (bytes) => { bytes[0] ^= 0xff; }); expect(ok).toBe(true); const after = new Uint8Array((await store.read('k'))!); expect(after[0]).not.toBe('h'.charCodeAt(0)); }); test('提交原子性:commitAll 一次性生效全部 pending', async () => { await store.write('a', new ArrayBuffer(1)); await store.write('b', new ArrayBuffer(1)); expect(await store.listKeys()).toEqual(['a', 'b']); store.commitAll(); expect((await store.listKeys()).sort()).toEqual(['a', 'b']); }); }); describe('[v0.8.0] 测试基座:installOPFSMock 真实语义', () => { beforeEach(() => clearRegistry()); test('keepExistingData:false 会截断(旧 mock 的缺陷 2)', async () => { installOPFSMock('trunc'); const backend = new OPFSBackend(); await backend.open('trunc'); await backend.write('f', new TextEncoder().encode('LONGCONTENT').buffer as ArrayBuffer); // 覆盖写更短内容 → 真实 OPFS 应截断为 5 字节 await backend.write('f', new TextEncoder().encode('short').buffer as ArrayBuffer); const read = await backend.read('f'); expect(decodeBytes(read)).toBe('short'); }); test('append 是真追加且不破坏已有内容', async () => { installOPFSMock('app'); const backend = new OPFSBackend(); await backend.open('app'); await backend.write('log', new TextEncoder().encode('AAA').buffer as ArrayBuffer); await backend.append('log', new TextEncoder().encode('BBB').buffer as ArrayBuffer); expect(decodeBytes(await backend.read('log'))).toBe('AAABBB'); }); test('读返回副本,跨 backend 实例隔离', async () => { installOPFSMock('iso'); const b1 = new OPFSBackend(); await b1.open('iso'); await b1.write('k', new TextEncoder().encode('v').buffer as ArrayBuffer); const b2 = new OPFSBackend(); await b2.open('iso'); const buf = await b2.read('k'); new Uint8Array(buf!)[0] = 0x5a; expect(decodeBytes(await b1.read('k'))).toBe('v'); }); test('崩溃后重开:已提交数据在,未提交的不在', async () => { const installed = installOPFSMock('crash'); const store = installed.store; store.restoreCommitted(new Map()); // 清空 await store.write('committed', new TextEncoder().encode('yes').buffer as ArrayBuffer); store.commitAll(); await store.write('pending', new TextEncoder().encode('no').buffer as ArrayBuffer); store.crashPending(); expect(decodeBytes(await store.read('committed'))).toBe('yes'); expect(await store.read('pending')).toBeNull(); }); }); describe('[v0.8.0] 测试基座:FaultyBackend', () => { beforeEach(() => clearRegistry()); test('注入的写失败必须真的让调用方拿到 rejection', async () => { installOPFSMock('faulty'); const faulty = new FaultyBackend(new OPFSBackend()); await faulty.open('faulty'); faulty.failNextWrite(); await expect(faulty.write('k', new ArrayBuffer(4))).rejects.toThrow(/injected write failure/); expect(faulty.injected.write).toBe(1); faulty.clearFaults(); await expect(faulty.write('k', new ArrayBuffer(4))).resolves.toBeUndefined(); }); test('静默丢弃写:调用方看到成功,但介质上没有(掉电场景)', async () => { installOPFSMock('drop'); const faulty = new FaultyBackend(new OPFSBackend()); await faulty.open('drop'); faulty.dropNextWrite(); await expect(faulty.write('k', new ArrayBuffer(4))).resolves.toBeUndefined(); expect(await faulty.read('k')).toBeNull(); // 静默丢失 expect(faulty.injected.dropped).toBe(1); }); test('撕裂追加:写到一半的 WAL 记录可被构造', async () => { installOPFSMock('torn'); const faulty = new FaultyBackend(new OPFSBackend()); await faulty.open('torn'); const record = new Uint8Array(100).fill(7); faulty.truncateNextAppendTo(37); await faulty.append('__wal_000000.bin', record.buffer as ArrayBuffer); const stored = await faulty.read('__wal_000000.bin'); expect(stored!.byteLength).toBe(37); expect(faulty.injected.truncated).toBe(1); }); test('crash() 丢弃未提交写(这才是崩溃,不是 close 优雅停机)', async () => { const store = new TransactionalFileStore({ dbName: 'crash2' }); const backend = new CrashableStoreBackend(store); const faulty = new FaultyBackend(backend); const text = new TextEncoder(); await faulty.write('saved', text.encode('KEEP').buffer as ArrayBuffer); backend.commit(); // 模拟 createWritable.close() 完成 await faulty.write('unsaved', text.encode('LOST').buffer as ArrayBuffer); expect(store.hasPending()).toBe(true); // 存在真实的"提交前崩溃窗口" expect(faulty.crash()).toBe(true); expect(store.hasPending()).toBe(false); expect(await store.read('unsaved')).toBeNull(); expect(decodeBytes(await store.read('saved'))).toBe('KEEP'); }); test('对照组:close() 是优雅停机,会把未提交写刷完 —— 因此不能用来模拟崩溃', async () => { const store = new TransactionalFileStore({ dbName: 'graceful' }); const backend = new CrashableStoreBackend(store); await backend.write('k', new TextEncoder().encode('PENDING').buffer as ArrayBuffer); expect(store.hasPending()).toBe(true); // close() 不丢弃 pending(真实 OPFSBackend.close 还会等写队列排空) await backend.close(); expect(store.hasPending()).toBe(true); // 提交后才可见 —— 证明"用 close 当崩溃"会掩盖崩溃窗口 backend.commit(); expect(decodeBytes(await store.read('k'))).toBe('PENDING'); }); });