/** * AriaEngine CryptoManager 加解密测试 * @module tests/engine/aria-crypto * * v0.2.6 补强:此前仅验证实例化,现在验证真实的加解密往返一致性。 */ import 'fake-indexeddb/auto'; import { CryptoManager, initCrypto, encryptPage, decryptPage, closeCrypto, } from '../../src/engine/aria/crypto'; function toBytes(data: ArrayBuffer): number[] { return Array.from(new Uint8Array(data)); } describe('AriaEngine — CryptoManager', () => { test('加解密往返一致', async () => { const cm = new CryptoManager(); await cm.init('test-password'); expect(cm.enabled).toBe(true); const original = new TextEncoder().encode('sensitive row data').buffer; const { iv, data } = await cm.encryptPage(original); // 密文应为乱码(与原文不同) expect(toBytes(data)).not.toEqual(toBytes(original)); const decrypted = await cm.decryptPage(iv, data); expect(toBytes(decrypted)).toEqual(toBytes(original)); cm.close(); expect(cm.enabled).toBe(false); }); test('错误密码解密失败(密钥不同)', async () => { const cm1 = new CryptoManager(); await cm1.init('correct-password'); const original = new TextEncoder().encode('top secret').buffer; const { iv, data } = await cm1.encryptPage(original); const cm2 = new CryptoManager(); await cm2.init('wrong-password'); await expect(cm2.decryptPage(iv, data)).rejects.toThrow(); cm1.close(); cm2.close(); }); test('不同 salt 派生不同密钥,解密互相失败', async () => { const cm1 = new CryptoManager(); await cm1.init('pwd', new Uint8Array(16).fill(1)); const cm2 = new CryptoManager(); await cm2.init('pwd', new Uint8Array(16).fill(2)); const original = new TextEncoder().encode('salt matters').buffer; const { iv, data } = await cm1.encryptPage(original); await expect(cm2.decryptPage(iv, data)).rejects.toThrow(); cm1.close(); cm2.close(); }); test('未初始化时加密抛错', async () => { const cm = new CryptoManager(); expect(cm.enabled).toBe(false); const data = new TextEncoder().encode('x').buffer; await expect(cm.encryptPage(data)).rejects.toThrow(/not initialized/); }); test('不同实例互不影响(独立密钥状态)', async () => { const cm1 = new CryptoManager(); await cm1.init('pwd-a'); const cm2 = new CryptoManager(); await cm2.init('pwd-b'); const original = new TextEncoder().encode('instance isolation').buffer; const { iv, data } = await cm1.encryptPage(original); await expect(cm2.decryptPage(iv, data)).rejects.toThrow(); // 各自解密自己的数据 const dec2orig = new TextEncoder().encode('two').buffer; const enc2 = await cm2.encryptPage(dec2orig); const dec2 = await cm2.decryptPage(enc2.iv, enc2.data); expect(toBytes(dec2)).toEqual(toBytes(dec2orig)); cm1.close(); cm2.close(); }); test('全局兼容层往返一致', async () => { await initCrypto('global-password'); const original = new TextEncoder().encode('global compat layer').buffer; const { iv, data } = await encryptPage(original); const decrypted = await decryptPage(iv, data); expect(toBytes(decrypted)).toEqual(toBytes(original)); closeCrypto(); }); test('大块数据(接近页面大小)往返一致', async () => { const cm = new CryptoManager(); await cm.init('page-size-test'); // 4KB 页面数据 const original = new Uint8Array(4096); for (let i = 0; i < 4096; i++) original[i] = i % 251; const { iv, data } = await cm.encryptPage(original.buffer); const decrypted = await cm.decryptPage(iv, data); expect(toBytes(decrypted)).toEqual(toBytes(original.buffer)); cm.close(); }); }); // =================================================================== // 真实存储路径:SSTable 加密 → 持久化 → 重载解密(v0.3.2 CI 修复) // =================================================================== describe('AriaEngine — SSTable 加密存储真实路径', () => { test('加密落盘后重载可完整解密', async () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { AriaEngine } = require('../../src/engine/aria/index'); // eslint-disable-next-line @typescript-eslint/no-var-requires const { createSchema } = require('../../src/table/schema'); const dbName = `enc-store-${Date.now()}`; await initCrypto('engine-store-password'); const engine = new AriaEngine({ storageBackend: 'indexeddb', walSyncMode: 'none', } as any); await engine.open(dbName, 1); await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, v: { type: 'number' }, })); await engine.insert('t', [{ id: '1', v: 42 }, { id: '2', v: 99 }]); // 强制刷盘:SSTable 经加密保存(encryptPage 真实路径) await (engine as any).lsm.flush(); await engine.close(); // 重载:SSTable 解密恢复 const engine2 = new AriaEngine({ storageBackend: 'indexeddb', walSyncMode: 'none', } as any); await engine2.open(dbName, 1); const rows = await engine2.find('t', { table: 't' }); expect(rows).toHaveLength(2); const byId = Object.fromEntries(rows.map((r: any) => [r.id, r.v])); expect(byId['1']).toBe(42); expect(byId['2']).toBe(99); await engine2.close(); closeCrypto(); }); });