test(P0): v0.8.0 验证基座与工程门禁根治

工作流 C-1 / C-3 前半 + 测试代码类型检查。

【故障注入基座】新增 tests/helpers/storage-harness.ts + faulty-backend.ts
- TransactionalFileStore:忠实 OPFS 提交语义(close 才可见)+ 字节级故障注入
  (failNextWrite/Append/Delete、truncateAppendTo 撕裂写、crashPending 真崩溃)
- 删除旧 opfs-mock:读返回内部引用、keepExistingData:false 不截断、close 空实现
  导致"提交前可见"等真实缺陷无法被测出(31 个测试文件迁移至新 harness)
- 删除 aria-opfs-backend 内的第三份重复 mock(含从未被断言使用的 writeCalls 死代码
  与 entry.content.subarray 恒等分支)
- FaultyBackend:包装任意 IStorageBackend 注入故障;crash() 明确区别于 close()
  (后者是优雅停机,会刷完写队列 —— 这正是此前所有"崩溃恢复"测试的真相)
- 16 条基座自测证明注入真的生效(含 close 不能当崩溃的对照组)

【覆盖率口径】jest.config.cjs
- 移除 '!src/**/index.ts'(该 glob 把 AriaEngine 主实现等 15 个实现文件整体
  排除出统计,与 v0.2.6 曾承认过的问题同源),改为只排除纯类型声明文件并附理由
- 新增 coverageThreshold 门禁(此前完全不存在)
- 真实基线:语句 90.66% / 分支 82.94% / 函数 94.36% / 行 93.43%
- 修正 testMatch 使 tests/helpers 下的测试可被发现

【测试代码类型检查】tsconfig.test.json + npm run typecheck:tests
- 修复 103 个测试代码类型错误(此前 babel 剥离类型 + tsconfig 排除 tests,全部隐藏)
- 新增 tests/helpers/assertions.ts:nonNull/decode/rows/object/engineMethod/expectCode
  以断言收窄替代 as any
- 消除 21 个 lint warning(含 v043-hardening 中定义后从未调用的 mockOPFS 死代码)
- parser.test.ts 12 处 toBeDefined() 空断言升级为结构断言(并新增 AND/OR 优先级用例,
  当前红灯,对应总账第 11 项,将在工作流 A 修复)

【版本契约】新增 tests/version-contract.test.ts
- 校验 src VERSION / package.json / dist 三者一致,替代两处硬编码版本字面量

【CI 门禁】.gitea/workflows/ci.yml
- lint 去掉 continue-on-error(此前永远不让 CI 变红)
- 新增 tests 类型检查、--coverage 覆盖率门禁、dist 与源码同步校验
- 版本 0.7.4 升至 0.8.0
This commit is contained in:
thzxx
2026-09-14 21:03:06 +08:00
parent c8b59bd16f
commit 0dba1abf2a
65 changed files with 1533 additions and 454 deletions
+122 -38
View File
@@ -1,69 +1,152 @@
/**
* Parser 边缘场景测试
*
* v0.8.0 强化说明:
* 1. 此前 12 处断言写着 `expect(ast.where).toBeDefined()` —— 既没有验证结构也没有验证
* 值,属于"空断言"。审计(AUDIT-query-layer / SQL 层)指出:`parser.test.ts` 无一处
* 断言混合 AND/OR 的**结构**,这正是 AND/OR 无优先级缺陷(总账第 11 项)能长期存活的原因。
* 2. 本文件此前 `parse()` 的返回类型是联合类型 `Statement`,直接访问 `.where` / `.columns`
* 在类型上不成立(tests 从不做类型检查,所以没人发现)。现在用类型收窄辅助函数显式
* 区分语句种类 —— 收窄失败即测试失败,而不是静默读到一个 undefined。
*/
import { parse } from '../../src/sql/parser';
import type {
SelectStatement,
InsertStatement,
UpdateStatement,
DeleteStatement,
CreateTableStatement,
} from '../../src/query/ast';
// ---------------------------------------------------------------------------
// 类型收窄辅助:断言语句种类并返回收窄后的类型
// (替代 `as any` —— 收窄失败会立刻让测试失败,而不是让断言落空)
// ---------------------------------------------------------------------------
function asSelect(stmt: ReturnType<typeof parse>): SelectStatement {
expect(stmt.type).toBe('SELECT');
if (stmt.type !== 'SELECT') throw new Error(`expected SELECT, got ${stmt.type}`);
return stmt;
}
function asInsert(stmt: ReturnType<typeof parse>): InsertStatement & { values: unknown[][] } {
expect(stmt.type).toBe('INSERT');
if (stmt.type !== 'INSERT') throw new Error(`expected INSERT, got ${stmt.type}`);
if (!stmt.values) throw new Error('INSERT statement is missing values');
return stmt as InsertStatement & { values: unknown[][] };
}
function asUpdate(stmt: ReturnType<typeof parse>): UpdateStatement {
expect(stmt.type).toBe('UPDATE');
if (stmt.type !== 'UPDATE') throw new Error(`expected UPDATE, got ${stmt.type}`);
return stmt;
}
function asDelete(stmt: ReturnType<typeof parse>): DeleteStatement {
expect(stmt.type).toBe('DELETE');
if (stmt.type !== 'DELETE') throw new Error(`expected DELETE, got ${stmt.type}`);
return stmt;
}
function asCreateTable(stmt: ReturnType<typeof parse>): CreateTableStatement {
expect(stmt.type).toBe('CREATE_TABLE');
if (stmt.type !== 'CREATE_TABLE') throw new Error(`expected CREATE_TABLE, got ${stmt.type}`);
return stmt;
}
describe('Parser 边缘场景', () => {
// ---- SELECT 扩展 ----
describe('SELECT 扩展', () => {
it('WHERE 多条件 AND', () => {
const ast = parse("SELECT * FROM users WHERE age > 18 AND name LIKE 'A%'");
expect(ast.type).toBe('SELECT');
expect(ast.where).toBeDefined();
const ast = asSelect(parse("SELECT * FROM users WHERE age > 18 AND name LIKE 'A%'"));
// v0.8.0: 断言真实结构而非 toBeDefined()
expect(ast.where).toEqual({
$and: [{ age: { $gt: 18 } }, { name: { $like: 'A%' } }],
});
});
it('WHERE IN 列表', () => {
const ast = parse('SELECT * FROM users WHERE id IN (1, 2, 3)');
expect(ast.type).toBe('SELECT');
expect(ast.where).toBeDefined();
const cond = (ast.where as any).id;
expect(cond.$in).toEqual([1, 2, 3]);
const ast = asSelect(parse('SELECT * FROM users WHERE id IN (1, 2, 3)'));
expect(ast.where).toEqual({ id: { $in: [1, 2, 3] } });
});
it('WHERE IS NULL', () => {
const ast = parse('SELECT * FROM users WHERE bio IS NULL');
expect(ast.type).toBe('SELECT');
expect(ast.where).toBeDefined();
const ast = asSelect(parse('SELECT * FROM users WHERE bio IS NULL'));
expect(ast.where).toEqual({ bio: { $eq: null } });
});
it('WHERE IS NOT NULL', () => {
const ast = parse('SELECT * FROM users WHERE bio IS NOT NULL');
expect(ast.type).toBe('SELECT');
const ast = asSelect(parse('SELECT * FROM users WHERE bio IS NOT NULL'));
expect(ast.where).toEqual({ bio: { $ne: null } });
});
it('WHERE NOT', () => {
const ast = parse("SELECT * FROM users WHERE NOT age = 18");
expect(ast.type).toBe('SELECT');
const ast = asSelect(parse('SELECT * FROM users WHERE NOT age = 18'));
expect(ast.where).toEqual({ $not: { age: { $eq: 18 } } });
});
it('WHERE 括号分组', () => {
const ast = parse("SELECT * FROM users WHERE (age > 18 OR age < 10) AND active = TRUE");
expect(ast.type).toBe('SELECT');
const ast = asSelect(parse('SELECT * FROM users WHERE (age > 18 OR age < 10) AND active = TRUE'));
expect(ast.where).toEqual({
$and: [{ $or: [{ age: { $gt: 18 } }, { age: { $lt: 10 } }] }, { active: { $eq: true } }],
});
});
// v0.8.0 新增:AND 优先级必须高于 OR(SQL 标准),此前是纯左折叠
it('AND 优先级高于 ORSQL 标准)', () => {
const ast = asSelect(parse('SELECT * FROM t WHERE a = 1 OR a = 2 AND b = 3'));
expect(ast.where).toEqual({
$or: [{ a: { $eq: 1 } }, { $and: [{ a: { $eq: 2 } }, { b: { $eq: 3 } }] }],
});
});
it('显式括号可覆盖默认优先级', () => {
const ast = asSelect(parse('SELECT * FROM t WHERE (a = 1 OR a = 2) AND b = 3'));
expect(ast.where).toEqual({
$and: [{ $or: [{ a: { $eq: 1 } }, { a: { $eq: 2 } }] }, { b: { $eq: 3 } }],
});
});
it('NOT 优先级高于 AND/OR', () => {
const ast = asSelect(parse('SELECT * FROM t WHERE NOT a = 1 OR b = 2'));
expect(ast.where).toEqual({
$or: [{ $not: { a: { $eq: 1 } } }, { b: { $eq: 2 } }],
});
});
it('多层 AND/OR 交替的结合性', () => {
const ast = asSelect(parse('SELECT * FROM t WHERE a = 1 AND b = 2 OR c = 3 AND d = 4'));
expect(ast.where).toEqual({
$or: [
{ $and: [{ a: { $eq: 1 } }, { b: { $eq: 2 } }] },
{ $and: [{ c: { $eq: 3 } }, { d: { $eq: 4 } }] },
],
});
});
it('ORDER BY 多列', () => {
const ast = parse('SELECT * FROM users ORDER BY age DESC, name ASC');
expect(ast.type).toBe('SELECT');
const ast = asSelect(parse('SELECT * FROM users ORDER BY age DESC, name ASC'));
expect(ast.orderBy).toHaveLength(2);
expect(ast.orderBy![0]).toEqual({ column: 'age', direction: 'desc' });
expect(ast.orderBy![1]).toEqual({ column: 'name', direction: 'asc' });
});
it('只有 LIMIT 没有 OFFSET', () => {
const ast = parse('SELECT * FROM users LIMIT 5');
const ast = asSelect(parse('SELECT * FROM users LIMIT 5'));
expect(ast.limit).toBe(5);
expect(ast.offset).toBeUndefined();
});
it('只有 OFFSET 没有 LIMIT', () => {
const ast = parse('SELECT * FROM users OFFSET 10');
expect(ast).toMatchObject({ offset: 10 });
const ast = asSelect(parse('SELECT * FROM users OFFSET 10'));
expect(ast.offset).toBe(10);
expect(ast.limit).toBeUndefined();
});
it('不带 WHERE 的 SELECT', () => {
const ast = parse('SELECT id, name FROM users ORDER BY id LIMIT 5');
const ast = asSelect(parse('SELECT id, name FROM users ORDER BY id LIMIT 5'));
expect(ast.where).toEqual({});
});
});
@@ -72,18 +155,18 @@ describe('Parser 边缘场景', () => {
describe('INSERT 扩展', () => {
it('不带列名的 INSERT', () => {
const ast = parse("INSERT INTO users VALUES ('1', 'Alice', 30)");
const ast = asInsert(parse("INSERT INTO users VALUES ('1', 'Alice', 30)"));
expect(ast.columns).toBeUndefined();
expect(ast.values).toEqual([['1', 'Alice', 30]]);
});
it('INSERT 布尔值和 NULL', () => {
const ast = parse('INSERT INTO users VALUES (TRUE, FALSE, NULL)');
const ast = asInsert(parse('INSERT INTO users VALUES (TRUE, FALSE, NULL)'));
expect(ast.values[0]).toEqual([true, false, null]);
});
it('INSERT 负数和浮点数', () => {
const ast = parse('INSERT INTO scores VALUES (-1, 3.14)');
const ast = asInsert(parse('INSERT INTO scores VALUES (-1, 3.14)'));
expect(ast.values[0]).toEqual([-1, 3.14]);
});
});
@@ -92,13 +175,13 @@ describe('Parser 边缘场景', () => {
describe('UPDATE 扩展', () => {
it('UPDATE 多列', () => {
const ast = parse("UPDATE users SET name = 'Bob', age = 26 WHERE id = '1'");
expect(ast.type).toBe('UPDATE');
const ast = asUpdate(parse("UPDATE users SET name = 'Bob', age = 26 WHERE id = '1'"));
expect(ast.sets).toEqual({ name: 'Bob', age: 26 });
expect(ast.where).toEqual({ id: { $eq: '1' } });
});
it('UPDATE 不带 WHERE', () => {
const ast = parse('UPDATE users SET active = FALSE');
const ast = asUpdate(parse('UPDATE users SET active = FALSE'));
expect(ast.where).toEqual({});
});
});
@@ -107,13 +190,15 @@ describe('Parser 边缘场景', () => {
describe('DELETE 扩展', () => {
it('DELETE 不带 WHERE', () => {
const ast = parse('DELETE FROM users');
const ast = asDelete(parse('DELETE FROM users'));
expect(ast.where).toEqual({});
});
it('DELETE 带复杂 WHERE', () => {
const ast = parse("DELETE FROM users WHERE age < 18 OR status = 'inactive'");
expect(ast.type).toBe('DELETE');
const ast = asDelete(parse("DELETE FROM users WHERE age < 18 OR status = 'inactive'"));
expect(ast.where).toEqual({
$or: [{ age: { $lt: 18 } }, { status: { $eq: 'inactive' } }],
});
});
});
@@ -121,8 +206,7 @@ describe('Parser 边缘场景', () => {
describe('DDL 扩展', () => {
it('CREATE TABLE 完整修饰符', () => {
const ast = parse("CREATE TABLE products (id STRING PRIMARY KEY, name STRING NOT NULL UNIQUE, price NUMBER DEFAULT 0, active BOOLEAN DEFAULT TRUE)");
expect(ast.type).toBe('CREATE_TABLE');
const ast = asCreateTable(parse("CREATE TABLE products (id STRING PRIMARY KEY, name STRING NOT NULL UNIQUE, price NUMBER DEFAULT 0, active BOOLEAN DEFAULT TRUE)"));
expect(ast.columns).toHaveLength(4);
expect(ast.columns[0]).toMatchObject({ name: 'id', primaryKey: true });
expect(ast.columns[1]).toMatchObject({ name: 'name', required: true, unique: true });
@@ -145,13 +229,13 @@ describe('Parser 边缘场景', () => {
describe('常量值解析', () => {
it('布尔值 TRUE/FALSE', () => {
const ast = parse("SELECT * FROM users WHERE active = TRUE");
expect(ast.where).toBeDefined();
const ast = asSelect(parse('SELECT * FROM users WHERE active = TRUE'));
expect(ast.where).toEqual({ active: { $eq: true } });
});
it('NULL 值', () => {
const ast = parse('SELECT * FROM users WHERE bio IS NULL');
expect(ast.where).toBeDefined();
const ast = asSelect(parse('SELECT * FROM users WHERE bio IS NULL'));
expect(ast.where).toEqual({ bio: { $eq: null } });
});
});
});