/** * v0.7.1 硬化回归测试 * * 覆盖: * - MetonaSqlark.create 静态工厂(README 示例 API 修复) * - close 未初始化防护(不再 TypeError) * - AriaEngine 未 open 防护统一(hasTable/getTableNames/getTableSchema) * - 事务回滚失败不掩盖原始错误 * - '__proto__' 列名拒绝(原型污染防护) */ import { MetonaSqlark } from '../src/core'; import { create } from '../src/index'; import { AriaEngine } from '../src/engine/aria/index'; import { MemoryEngine } from '../src/engine/memory'; import { TransactionManager } from '../src/transaction/index'; import { createSchema } from '../src/table/schema'; function uniqueDB(): string { return `v071-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; } describe('v0.7.1 — MetonaSqlark.create 静态工厂', () => { it('类静态 create 可用(与 api create 行为一致)', async () => { const db = await MetonaSqlark.create({ name: uniqueDB(), mode: 'memory' }); expect(db.isReady()).toBe(true); await db.defineTable('t', { id: { type: 'string', primaryKey: true } }); await db.query('INSERT INTO t VALUES (\'1\')'); const rows = await db.query('SELECT * FROM t') as Record[]; expect(rows).toHaveLength(1); await db.close(); }); it('独立 create 函数仍可用', async () => { const db = await create({ name: uniqueDB(), mode: 'memory' }); expect(db.isReady()).toBe(true); await db.close(); }); it('init 前 close 不崩溃', async () => { const db = new MetonaSqlark({ name: uniqueDB(), mode: 'memory' }); await expect(db.close()).resolves.toBeUndefined(); expect(db.isReady()).toBe(false); }); }); describe('v0.7.1 — AriaEngine 未 open 防护统一', () => { it('close 后 getTableNames/hasTable/getTableSchema 抛 DB_NOT_OPEN', async () => { const eng = new AriaEngine({ storageBackend: 'memory' }); await eng.open(uniqueDB(), 1); await eng.close(); await expect(eng.getTableNames()).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); await expect(eng.hasTable('t')).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); await expect(eng.getTableSchema('t')).rejects.toMatchObject({ code: 'DB_NOT_OPEN' }); }); it('open 后正常返回(行为不变)', async () => { const eng = new AriaEngine({ storageBackend: 'memory' }); await eng.open(uniqueDB(), 1); expect(await eng.getTableNames()).toEqual([]); expect(await eng.hasTable('t')).toBe(false); expect(await eng.getTableSchema('t')).toBeNull(); await eng.close(); }); }); describe('v0.7.1 — 事务回滚失败不掩盖原始错误', () => { class RollbackFailsEngine extends MemoryEngine { async rollbackTransaction(): Promise { throw new Error('rollback boom'); } async beginTransaction(): Promise { /* ok */ } async commitTransaction(): Promise { /* ok */ } } it('事务体抛错 + 回滚失败 → 抛原始事务错误', async () => { const eng = new RollbackFailsEngine(); const mgr = new TransactionManager(eng); await expect(mgr.execute(async () => { throw new Error('original tx failure'); })).rejects.toThrow('original tx failure'); }); }); describe('v0.7.1 — __proto__ 列名拒绝', () => { it('动态构造列映射(null 原型)含 __proto__ 列名被拒绝', () => { // 对象字面量的 `__proto__:` 在字面量层就触发原型 setter(键不存在); // 真实攻击面是动态构造的列映射(SQL 路径 / Object.create(null))——键可被 // Object.entries 看到,校验层必须拒绝 const cols: Record = Object.create(null) as Record; cols.id = { type: 'string', primaryKey: true }; cols.__proto__ = { type: 'string' }; expect(() => createSchema('t', cols as never)).toThrow('"__proto__"'); }); it('SQL CREATE TABLE 拒绝 __proto__ 列名(列不静默丢失)', async () => { const db = await MetonaSqlark.create({ name: uniqueDB(), mode: 'memory' }); await expect(db.query('CREATE TABLE t (id STRING PRIMARY KEY, __proto__ STRING)')) .rejects.toMatchObject({ code: 'SCHEMA_ERROR' }); await db.close(); }); });