fix: v0.7.1 API 修复与防御统一 — static create / 未 open 防护 / 原型污染 / lint 清零
CI / test (22.x) (push) Successful in 17m24s
CI / e2e (push) Successful in 10m39s
CI / test (18.x) (push) Successful in 19m5s
CI / test (20.x) (push) Successful in 18m4s
CI / test (24.x) (push) Successful in 23m19s

- 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:
thzxx
2026-08-13 11:14:42 +08:00
parent 57415975ea
commit cbe407eb49
25 changed files with 1620 additions and 1375 deletions
+1 -1
View File
@@ -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');
});
});
+1 -1
View File
@@ -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', {
+102
View File
@@ -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();
});
});