/** * 生产级异常场景测试 — KVStore / KVStoreEngine 边界与故障 * * 覆盖: * 1. KVStore 边界:空 key/value、超大 value、特殊字符 key、未 open、重复 open/close、clear 后写 * 2. KVStoreEngine 异常:未 open 操作、重复 close、事务嵌套、无事务 commit/rollback、 * 级联环(A→B→A)、主键变更+索引、多表事务原子性 * 3. SharedMemoryBackend 全分支:append 已有 key、writeMany/deleteMany、close 后安全 * 4. 日志解析损坏分支(onCorrupt false 提前停止) */ import { KVStore } from '../src/engine/kvstore/index'; import { SharedMemoryBackend } from '../src/engine/kvstore/shared_memory_medium'; import { encodeLogRecord, parseLogRecords } from '../src/engine/kvstore/log'; import { KVStoreEngine } from '../src/engine/kvstore_engine'; import { createSchema } from '../src/table/schema'; let counter = 0; function uniqueDB(): string { return `abn-${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 6)}`; } const enc = (s: string) => new TextEncoder().encode(s).buffer as ArrayBuffer; const dec = (b: ArrayBuffer | null) => (b ? new TextDecoder().decode(b) : null); beforeEach(() => { SharedMemoryBackend.clearRegistry(); }); // =================================================================== // KVStore 边界 // =================================================================== describe('KVStore — 边界与故障', () => { it('空 value / 空字符串 key 可正常读写', async () => { const kv = new KVStore(new SharedMemoryBackend(), 0); await kv.open(uniqueDB()); await kv.put('empty', new ArrayBuffer(0)); expect((await kv.get('empty'))!.byteLength).toBe(0); await kv.put('', enc('empty-key')); expect(dec(await kv.get(''))).toBe('empty-key'); await kv.close(); }); it('超大 value(1MB)写入恢复完整', async () => { const dbName = uniqueDB(); const medium = new SharedMemoryBackend(); const kv = new KVStore(medium, 0); await kv.open(dbName); const big = new Uint8Array(1024 * 1024); for (let i = 0; i < big.length; i++) big[i] = i & 0xff; await kv.put('big', big.buffer as ArrayBuffer); await kv.close(); const kv2 = new KVStore(medium, 0); await kv2.open(dbName); const back = await kv2.get('big'); expect(back!.byteLength).toBe(1024 * 1024); const backBytes = new Uint8Array(back!); expect(backBytes[500000]).toBe(500000 & 0xff); await kv2.close(); }); it('特殊字符 key(中文/emoji/含冒号/超长)读写恢复', async () => { const dbName = uniqueDB(); const medium = new SharedMemoryBackend(); const kv = new KVStore(medium, 0); await kv.open(dbName); const keys = ['中文键', 'emoji-😀-key', 'a:b:c', 'x'.repeat(500)]; for (let i = 0; i < keys.length; i++) { await kv.put(keys[i], enc(`v${i}`)); } await kv.checkpoint(); await kv.close(); const kv2 = new KVStore(medium, 0); await kv2.open(dbName); for (let i = 0; i < keys.length; i++) { expect(dec(await kv2.get(keys[i]))).toBe(`v${i}`); } await kv2.close(); }); it('未 open 的 KVStore 介质为空(get 返回 null,操作安全)', async () => { const kv = new KVStore(new SharedMemoryBackend(), 0); expect(kv.isOpen()).toBe(false); expect(await kv.get('x')).toBeNull(); expect(kv.size()).toBe(0); }); it('重复 open / 重复 close 幂等', async () => { const dbName = uniqueDB(); const kv = new KVStore(new SharedMemoryBackend(), 0); await kv.open(dbName); await kv.open(dbName); // 幂等 await kv.put('a', enc('A')); await kv.close(); await kv.close(); // 幂等 expect(kv.isOpen()).toBe(false); }); it('clear 后立即写入正常,重开数据只有新写入', async () => { const dbName = uniqueDB(); const medium = new SharedMemoryBackend(); const kv = new KVStore(medium, 0); await kv.open(dbName); await kv.put('old', enc('OLD')); await kv.clear(); await kv.put('new', enc('NEW')); await kv.close(); const kv2 = new KVStore(medium, 0); await kv2.open(dbName); expect(await kv2.exists('old')).toBe(false); expect(dec(await kv2.get('new'))).toBe('NEW'); await kv2.close(); }); it('大量小 key(1 万)快速写入 + 恢复', async () => { const dbName = uniqueDB(); const medium = new SharedMemoryBackend(); const kv = new KVStore(medium, 0); await kv.open(dbName); const batch: Record = {}; for (let i = 0; i < 10000; i++) batch[`k${i}`] = enc(`v${i}`); await kv.putMany(batch); await kv.close(); const kv2 = new KVStore(medium, 0); await kv2.open(dbName); expect(kv2.size()).toBe(10000); expect(dec(await kv2.get('k9999'))).toBe('v9999'); await kv2.close(); }, 300000); it('写入失败后 KVStore 继续可用(错误不污染后续操作)', async () => { const dbName = uniqueDB(); const medium = new SharedMemoryBackend(); const kv = new KVStore(medium, 0); await kv.open(dbName); // 注入一次失败 const origAppend = medium.append!.bind(medium); medium.append = async () => { throw new Error('boom'); }; await expect(kv.put('x', enc('X'))).rejects.toMatchObject({ code: 'KV_LOG_ERROR' }); medium.append = origAppend; // 后续写入正常 await kv.put('y', enc('Y')); await kv.close(); const kv2 = new KVStore(medium, 0); await kv2.open(dbName); expect(dec(await kv2.get('y'))).toBe('Y'); await kv2.close(); }); }); // =================================================================== // KVStoreEngine 异常场景 // =================================================================== describe('KVStoreEngine — 异常与一致性', () => { async function createEngine(): Promise { const e = new KVStoreEngine(new SharedMemoryBackend()); await e.open(uniqueDB(), 1); return e; } it('未 open 时所有操作抛 DB_NOT_OPEN', async () => { const e = new KVStoreEngine(new SharedMemoryBackend()); await expect(e.getTableNames()).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); await expect(e.insert('t', [{ id: '1' }])).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); await expect(e.find('t', { table: 't' })).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); await expect(e.count('t')).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); await expect(e.beginTransaction()).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); }); it('重复 close 幂等', async () => { const e = await createEngine(); await e.close(); await e.close(); expect(e.isOpen()).toBe(false); }); it('不存在的表操作抛 TABLE_NOT_FOUND', async () => { const e = await createEngine(); await expect(e.insert('ghost', [{ id: '1' }])).rejects.toMatchObject({ code: 'TABLE_NOT_FOUND' }); await expect(e.update('ghost', { table: 'ghost' }, {})).rejects.toMatchObject({ code: 'TABLE_NOT_FOUND' }); await expect(e.delete('ghost', { table: 'ghost' })).rejects.toMatchObject({ code: 'TABLE_NOT_FOUND' }); await expect(e.count('ghost')).rejects.toMatchObject({ code: 'TABLE_NOT_FOUND' }); await e.close(); }); it('事务嵌套 begin 两次 → TX_ACTIVE', async () => { const e = await createEngine(); await e.beginTransaction(); await expect(e.beginTransaction()).rejects.toMatchObject({ code: 'TX_ACTIVE' }); await e.rollbackTransaction(); await e.close(); }); it('无事务 commit/rollback → TX_NONE', async () => { const e = await createEngine(); await expect(e.commitTransaction()).rejects.toMatchObject({ code: 'TX_NONE' }); await expect(e.rollbackTransaction()).rejects.toMatchObject({ code: 'TX_NONE' }); await e.close(); }); it('事务中途失败自动回滚(多表原子性:不出现部分提交)', async () => { const e = await createEngine(); await e.createTable(createSchema('t1', { id: { type: 'string', primaryKey: true } })); await e.createTable(createSchema('t2', { id: { type: 'string', primaryKey: true } })); await e.beginTransaction(); await e.insert('t1', [{ id: 'a' }]); await e.insert('t2', [{ id: 'b' }]); // 模拟 commit 失败:注入日志追加故障(KVStore 写走日志 append) const medium = (e as any).kv.medium; const origAppend = medium.append.bind(medium); medium.append = async () => { throw new Error('disk full'); }; await expect(e.commitTransaction()).rejects.toMatchObject({ code: 'KV_LOG_ERROR' }); medium.append = origAppend; // commit 失败后事务未完成:手动回滚 → 内存恢复 await e.rollbackTransaction(); expect(await e.count('t1')).toBe(0); expect(await e.count('t2')).toBe(0); await e.close(); // 重启:磁盘也不应有部分提交(事务内写入全部未持久化) const medium2 = new SharedMemoryBackend(); const e2 = new KVStoreEngine(medium2); await e2.open((e as any).dbName, 1); expect(await e2.count('t1')).toBe(0); expect(await e2.count('t2')).toBe(0); await e2.close(); }); it('多表事务 commit 单次原子:崩溃后要么全提交要么全无', async () => { const dbName = uniqueDB(); const medium = new SharedMemoryBackend(); const e = new KVStoreEngine(medium); await e.open(dbName, 1); await e.createTable(createSchema('t1', { id: { type: 'string', primaryKey: true } })); await e.createTable(createSchema('t2', { id: { type: 'string', primaryKey: true } })); await e.beginTransaction(); await e.insert('t1', [{ id: 'a' }]); await e.insert('t2', [{ id: 'b' }]); await e.commitTransaction(); // commit 后的数据在日志(未 checkpoint)—— 崩溃模拟 await e.close(); const e2 = new KVStoreEngine(medium); await e2.open(dbName, 1); expect(await e2.count('t1')).toBe(1); expect(await e2.count('t2')).toBe(1); await e2.close(); }); it('级联环(A→B→A)删除不无限递归', async () => { const e = await createEngine(); await e.createTable(createSchema('ta', { id: { type: 'string', primaryKey: true }, ref_b: { type: 'string', references: 'tb.id', onDelete: 'CASCADE' }, })); await e.createTable(createSchema('tb', { id: { type: 'string', primaryKey: true }, ref_a: { type: 'string', references: 'ta.id', onDelete: 'CASCADE' }, })); await e.insert('ta', [{ id: 'a1', ref_b: 'b1' }]); await e.insert('tb', [{ id: 'b1', ref_a: 'a1' }]); // 删除不应死循环(visited 保护) await e.delete('ta', { table: 'ta', where: { id: 'a1' } }); expect(await e.count('ta')).toBe(0); await e.close(); }); it('主键变更后索引一致(旧值查询无结果,新值查询命中)', async () => { const e = await createEngine(); await e.createTable(createSchema('items', { id: { type: 'string', primaryKey: true }, tag: { type: 'string', index: true }, })); await e.insert('items', [{ id: '1', tag: 'old' }]); await e.update('items', { table: 'items', where: { id: '1' } }, { id: '10', tag: 'new' }); // 旧主键查询空 expect(await e.find('items', { table: 'items', where: { id: '1' } })).toHaveLength(0); // 新主键命中 expect(await e.find('items', { table: 'items', where: { id: '10' } })).toHaveLength(1); // 索引:旧 tag 空,新 tag 命中 expect(await e.find('items', { table: 'items', where: { tag: 'old' } })).toHaveLength(0); expect(await e.find('items', { table: 'items', where: { tag: 'new' } })).toHaveLength(1); await e.close(); // 重启后仍一致 const e2 = new KVStoreEngine(new SharedMemoryBackend()); await e2.open((e as any).dbName, 1); expect(await e2.find('items', { table: 'items', where: { tag: 'new' } })).toHaveLength(1); expect(await e2.find('items', { table: 'items', where: { id: '10' } })).toHaveLength(1); await e2.close(); }); it('空表操作(count/find/clear 空表)正常', async () => { const e = await createEngine(); await e.createTable(createSchema('empty', { id: { type: 'string', primaryKey: true } })); expect(await e.count('empty')).toBe(0); expect(await e.find('empty', { table: 'empty' })).toHaveLength(0); await e.clear('empty'); await e.close(); }); it('大批量 delete(5000 行)持久化后重启正确', async () => { const dbName = uniqueDB(); const medium = new SharedMemoryBackend(); const e = new KVStoreEngine(medium); await e.open(dbName, 1); await e.createTable(createSchema('bulk', { id: { type: 'number', primaryKey: true } })); const rows = [] as Record[]; for (let i = 0; i < 5000; i++) rows.push({ id: i }); await e.insert('bulk', rows); await e.delete('bulk', { table: 'bulk', where: { id: { $gte: 4000 } } }); expect(await e.count('bulk')).toBe(4000); await e.close(); const e2 = new KVStoreEngine(medium); await e2.open(dbName, 1); expect(await e2.count('bulk')).toBe(4000); const remaining = await e2.find('bulk', { table: 'bulk', where: { id: { $gte: 3000 } } }); expect(remaining).toHaveLength(1000); await e2.close(); }, 300000); }); // =================================================================== // SharedMemoryBackend 全分支 + 日志解析损坏分支 // =================================================================== describe('SharedMemoryBackend — 全分支', () => { it('append 到已存在 key(拼接)与不存在 key(新建)', async () => { const medium = new SharedMemoryBackend(); await medium.open('sm-1'); await medium.append('log', enc('AAA')); await medium.append('log', enc('BBB')); expect(dec(await medium.read('log'))).toBe('AAABBB'); await medium.close(); }); it('writeMany / deleteMany 原子批量', async () => { const medium = new SharedMemoryBackend(); await medium.open('sm-2'); await medium.writeMany({ a: enc('1'), b: enc('2'), c: enc('3') }); expect(dec(await medium.read('b'))).toBe('2'); await medium.deleteMany(['a', 'c']); expect(await medium.exists('a')).toBe(false); expect(await medium.exists('b')).toBe(true); // writeMany 覆盖已有 key await medium.writeMany({ b: enc('B2') }); expect(dec(await medium.read('b'))).toBe('B2'); await medium.close(); }); it('close 后操作安全(不抛错;写入不持久化)', async () => { const medium = new SharedMemoryBackend(); await medium.open('sm-3'); await medium.write('k', enc('V')); await medium.close(); expect(await medium.read('k')).toBeNull(); // close 后内存清空 await medium.write('late', enc('X')); // 不抛错(游离内存,不持久化) await medium.delete('k'); // 不抛错 await medium.clear(); // 不抛错 }); it('跨实例共享:close 后新实例可读(持久化语义)', async () => { const medium1 = new SharedMemoryBackend(); await medium1.open('sm-4'); await medium1.write('k', enc('PERSIST')); await medium1.close(); const medium2 = new SharedMemoryBackend(); await medium2.open('sm-4'); expect(dec(await medium2.read('k'))).toBe('PERSIST'); await medium2.close(); }); }); describe('KVStore 日志解析 — 损坏分支', () => { it('onCorrupt 返回 false → 立即停止扫描', () => { const good1 = encodeLogRecord(1, { a: enc('1') }); const bad = encodeLogRecord(2, { b: enc('2') }); bad[10] ^= 0xff; // 破坏 const good2 = encodeLogRecord(3, { c: enc('3') }); const combined = new Uint8Array(good1.byteLength + bad.byteLength + good2.byteLength); combined.set(good1, 0); combined.set(bad, good1.byteLength); combined.set(good2, good1.byteLength + bad.byteLength); const seqs: number[] = []; let corruptCalls = 0; const count = parseLogRecords(combined, (r) => seqs.push(r.seq), () => { corruptCalls++; return false; // 停止 }); expect(count).toBe(1); // 只有第一条 expect(seqs).toEqual([1]); expect(corruptCalls).toBe(1); }); it('损坏条目(keyLen 越界)→ 记录被丢弃', () => { // 手工构造:entry 的 keyLen 超长导致解析失败 const buf = new ArrayBuffer(4 + 4 + 4 + 1 + 4 + 0xFFFFFF + 4); const view = new DataView(buf); let o = 0; const recLen = buf.byteLength - 4; view.setUint32(o, recLen, false); o += 4; view.setUint32(o, 1, false); o += 4; // seq view.setUint32(o, 1, false); o += 4; // entryCount view.setUint8(o, 1); o += 1; // op PUT view.setUint32(o, 0xFFFFFF, false); o += 4; // keyLen 超长 // crc(随便填,解析会在 keyLen 越界处停止) view.setUint32(o, 0, false); const data = new Uint8Array(buf); let corrupt = 0; const count = parseLogRecords(data, () => {}, () => { corrupt++; return false; }); expect(count).toBe(0); expect(corrupt).toBe(1); }); }); // =================================================================== // AriaEngine 级联环(与 MemoryEngine 对齐) // =================================================================== describe('AriaEngine — 级联环保护', () => { it('A→B→A 级联环删除不无限递归(visited 保护)', async () => { const { AriaEngine } = require('../src/engine/aria/index'); const engine = new AriaEngine({ storageBackend: 'memory' }); await engine.open(`aria-loop-${Date.now()}-${Math.random()}`, 1); await engine.createTable(createSchema('ta', { id: { type: 'string', primaryKey: true }, ref_b: { type: 'string', references: 'tb.id', onDelete: 'CASCADE' }, })); await engine.createTable(createSchema('tb', { id: { type: 'string', primaryKey: true }, ref_a: { type: 'string', references: 'ta.id', onDelete: 'CASCADE' }, })); await engine.insert('ta', [{ id: 'a1', ref_b: 'b1' }]); await engine.insert('tb', [{ id: 'b1', ref_a: 'a1' }]); // 不应死循环(RangeError) await engine.delete('ta', { table: 'ta', where: { id: 'a1' } }); expect(await engine.count('ta')).toBe(0); expect(await engine.count('tb')).toBe(0); await engine.close(); }); it('更深级联链(A→B→C→D)删除完整', async () => { const { AriaEngine } = require('../src/engine/aria/index'); const engine = new AriaEngine({ storageBackend: 'memory' }); await engine.open(`aria-chain-${Date.now()}-${Math.random()}`, 1); await engine.createTable(createSchema('ta', { id: { type: 'string', primaryKey: true }, ref: { type: 'string', references: 'tb.id', onDelete: 'CASCADE' }, })); await engine.createTable(createSchema('tb', { id: { type: 'string', primaryKey: true }, ref: { type: 'string', references: 'tc.id', onDelete: 'CASCADE' }, })); await engine.createTable(createSchema('tc', { id: { type: 'string', primaryKey: true }, ref: { type: 'string', references: 'td.id', onDelete: 'CASCADE' }, })); await engine.createTable(createSchema('td', { id: { type: 'string', primaryKey: true }, })); await engine.insert('td', [{ id: 'd1' }]); await engine.insert('tc', [{ id: 'c1', ref: 'd1' }]); await engine.insert('tb', [{ id: 'b1', ref: 'c1' }]); await engine.insert('ta', [{ id: 'a1', ref: 'b1' }]); // 删除链尾 td.d1 → 级联 tc(引用 td)→ tb(引用 tc)→ ta(引用 tb) await engine.delete('td', { table: 'td', where: { id: 'd1' } }); expect(await engine.count('ta')).toBe(0); expect(await engine.count('tb')).toBe(0); expect(await engine.count('tc')).toBe(0); expect(await engine.count('td')).toBe(0); await engine.close(); }); });