fix: v0.7.1 API 修复与防御统一 — static create / 未 open 防护 / 原型污染 / lint 清零
- MetonaSqlark.create 静态工厂(README 示例在 ESM/Node 下此前 TypeError), 独立 create 函数委托静态实现 - close 未初始化防御(engine undefined 不再崩溃) - AriaEngine hasTable/getTableNames/getTableSchema 统一 ensureOpen - 事务回滚失败不掩盖原始错误 - __proto__ 列名防护:Executor 列映射 Object.create(null) + schema 校验拒绝 - lint 清零(移除 5 处未使用导入) 测试 1147 → 1155(73 套件);行覆盖率 89.8%;版本 0.7.1
This commit is contained in:
@@ -28,7 +28,7 @@ beforeEach(() => { installOPFSMock(new Map()); });
|
||||
|
||||
describe('[v0.2.5] P0-1: 版本号统一', () => {
|
||||
test('VERSION 常量为当前版本(0.6.0)', () => {
|
||||
expect(VERSION).toBe('0.7.0');
|
||||
expect(VERSION).toBe('0.7.1');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -403,7 +403,7 @@ describe('[v0.3.3] P1-9: Savepoint + MVCC 一致性', () => {
|
||||
|
||||
describe('[v0.3.3] 端到端', () => {
|
||||
test('全部修复点可共存于 MetonaSqlark API', async () => {
|
||||
expect(VERSION).toBe('0.7.0');
|
||||
expect(VERSION).toBe('0.7.1');
|
||||
const db = new MetonaSqlark({ name: `e2e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, mode: 'memory' });
|
||||
await db.init();
|
||||
await db.defineTable('users', {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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<string, unknown>[];
|
||||
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<void> {
|
||||
throw new Error('rollback boom');
|
||||
}
|
||||
async beginTransaction(): Promise<void> { /* ok */ }
|
||||
async commitTransaction(): Promise<void> { /* 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<string, unknown> = Object.create(null) as Record<string, unknown>;
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user