release: v0.5.1 — 存储后端生产级硬化(CRC-32/全库加密/WAL分片/页面化存储/多标签页锁/e2e)+ 深度审查修复(假实现接线/死代码清理)
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* AriaEngine — 多标签页独占锁(Web Locks)测试
|
||||
*
|
||||
* 覆盖:
|
||||
* 1. 第一个引擎持锁 → 第二个引擎打开抛 ARIA_LOCKED
|
||||
* 2. 关闭后锁释放 → 可再次打开
|
||||
* 3. 环境不支持 Web Locks → 降级打开(返回 false 语义 + 警告)
|
||||
* 4. open 失败(如加密密码错误)→ 锁释放(其他标签页可打开)
|
||||
* 5. 锁名按库隔离(不同库不互斥)
|
||||
*/
|
||||
import { AriaEngine } from '../../src/engine/aria/index';
|
||||
import { DatabaseLock, lockName } from '../../src/engine/aria/locks';
|
||||
import { createSchema } from '../../src/table/schema';
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
let idbCounter = 0;
|
||||
function uniqueDB(): string {
|
||||
return `lock-${Date.now()}-${++idbCounter}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Web Locks mock:真实语义(ifAvailable / 回调结束锁自动释放)
|
||||
// ===================================================================
|
||||
let lockHolders: Map<string, true>;
|
||||
|
||||
function installLocksMock() {
|
||||
lockHolders = new Map();
|
||||
Object.defineProperty(globalThis, 'navigator', {
|
||||
value: {
|
||||
locks: {
|
||||
request: async (_name: string, opts: { ifAvailable?: boolean }, cb: (lock: { name: string } | null) => Promise<void> | void) => {
|
||||
const name = _name;
|
||||
if (lockHolders.has(name)) {
|
||||
if (opts?.ifAvailable) {
|
||||
await cb(null);
|
||||
return;
|
||||
}
|
||||
throw new Error('unreachable: non-ifAvailable request not used');
|
||||
}
|
||||
lockHolders.set(name, true);
|
||||
try {
|
||||
// 真实 Web Locks 语义:回调结束 → 锁自动释放
|
||||
await cb({ name });
|
||||
} finally {
|
||||
lockHolders.delete(name);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function removeLocksMock() {
|
||||
Object.defineProperty(globalThis, 'navigator', {
|
||||
value: {},
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// DatabaseLock 单元
|
||||
// ===================================================================
|
||||
describe('AriaEngine — DatabaseLock 单元', () => {
|
||||
beforeEach(installLocksMock);
|
||||
|
||||
it('acquire 持锁 → 二次 acquire 抛 ARIA_LOCKED → release 后恢复', async () => {
|
||||
const lock1 = new DatabaseLock();
|
||||
expect(await lock1.acquire('db-a')).toBe(true);
|
||||
expect(lock1.isAcquired()).toBe(true);
|
||||
expect(lock1.isSupported()).toBe(true);
|
||||
expect(lockHolders.has(lockName('db-a'))).toBe(true);
|
||||
|
||||
const lock2 = new DatabaseLock();
|
||||
await expect(lock2.acquire('db-a')).rejects.toMatchObject({ code: 'ARIA_LOCKED' });
|
||||
expect(lock2.isAcquired()).toBe(false);
|
||||
|
||||
await lock1.release();
|
||||
expect(lock1.isAcquired()).toBe(false);
|
||||
expect(lockHolders.has(lockName('db-a'))).toBe(false);
|
||||
|
||||
const lock3 = new DatabaseLock();
|
||||
expect(await lock3.acquire('db-a')).toBe(true);
|
||||
await lock3.release();
|
||||
});
|
||||
|
||||
it('不同库名互不阻塞', async () => {
|
||||
const l1 = new DatabaseLock();
|
||||
const l2 = new DatabaseLock();
|
||||
expect(await l1.acquire('db-x')).toBe(true);
|
||||
expect(await l2.acquire('db-y')).toBe(true);
|
||||
await l1.release();
|
||||
await l2.release();
|
||||
});
|
||||
|
||||
it('无 Web Locks 环境 → acquire 返回 false(降级)', async () => {
|
||||
removeLocksMock();
|
||||
const lock = new DatabaseLock();
|
||||
expect(await lock.acquire('db-z')).toBe(false);
|
||||
expect(lock.isSupported()).toBe(false);
|
||||
expect(lock.isAcquired()).toBe(false);
|
||||
});
|
||||
|
||||
it('重复 release 幂等', async () => {
|
||||
const lock = new DatabaseLock();
|
||||
await lock.acquire('db-r');
|
||||
await lock.release();
|
||||
await lock.release();
|
||||
expect(lock.isAcquired()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ===================================================================
|
||||
// AriaEngine 集成 — 多标签页锁
|
||||
// ===================================================================
|
||||
describe('AriaEngine — 多标签页锁(集成)', () => {
|
||||
beforeEach(installLocksMock);
|
||||
|
||||
it('第二个标签页打开同一库 → ARIA_LOCKED', async () => {
|
||||
const dbName = uniqueDB();
|
||||
const engine1 = new AriaEngine({ storageBackend: 'indexeddb' });
|
||||
await engine1.open(dbName, 1);
|
||||
await engine1.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
||||
|
||||
const engine2 = new AriaEngine({ storageBackend: 'indexeddb' });
|
||||
await expect(engine2.open(dbName, 1)).rejects.toMatchObject({ code: 'ARIA_LOCKED' });
|
||||
// engine2 未持锁、未 opened
|
||||
expect((engine2 as any).opened).toBe(false);
|
||||
|
||||
await engine1.close();
|
||||
|
||||
// 释放后可打开
|
||||
const engine3 = new AriaEngine({ storageBackend: 'indexeddb' });
|
||||
await engine3.open(dbName, 1);
|
||||
expect(await engine3.count('t')).toBe(0);
|
||||
await engine3.close();
|
||||
});
|
||||
|
||||
it('open 失败(错误密码)→ 锁释放,其他标签页可打开', async () => {
|
||||
const dbName = uniqueDB();
|
||||
const engine1 = new AriaEngine({ storageBackend: 'indexeddb', encryption: { password: 'pw' } });
|
||||
await engine1.open(dbName, 1);
|
||||
await engine1.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
||||
await engine1.close();
|
||||
|
||||
// 错误密码 → open 失败
|
||||
const engineBad = new AriaEngine({ storageBackend: 'indexeddb', encryption: { password: 'wrong' } });
|
||||
await expect(engineBad.open(dbName, 1)).rejects.toMatchObject({ code: 'ARIA_DECRYPT_ERROR' });
|
||||
// 失败后锁已释放(未泄漏)
|
||||
expect(lockHolders.has(lockName(dbName))).toBe(false);
|
||||
|
||||
// 正确密码可打开
|
||||
const engineOk = new AriaEngine({ storageBackend: 'indexeddb', encryption: { password: 'pw' } });
|
||||
await engineOk.open(dbName, 1);
|
||||
await engineOk.close();
|
||||
});
|
||||
|
||||
it('无 Web Locks 环境 → 降级打开(不抛错)', async () => {
|
||||
removeLocksMock();
|
||||
const dbName = uniqueDB();
|
||||
const engine = new AriaEngine({ storageBackend: 'indexeddb' });
|
||||
await engine.open(dbName, 1);
|
||||
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
||||
await engine.insert('t', [{ id: '1' }]);
|
||||
expect(await engine.count('t')).toBe(1);
|
||||
await engine.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user