工作流 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
142 lines
7.5 KiB
TypeScript
142 lines
7.5 KiB
TypeScript
/**
|
|
* AriaEngine 最终扩展测试 — 全部 MemoryBackend
|
|
*/
|
|
import { AriaEngine } from '../../src/engine/aria/index';
|
|
import { createSchema } from '../../src/table/schema';
|
|
import { MemoryBackend } from '../../src/engine/aria/store/backend';
|
|
|
|
import { resetOPFSMock } from '../helpers/storage-harness';
|
|
|
|
beforeEach(() => { resetOPFSMock(); });
|
|
|
|
describe('AriaEngine — 批量扩展测试', () => {
|
|
let engine: AriaEngine;
|
|
beforeEach(async () => { engine = new AriaEngine({ storageBackend: 'memory' }); await engine.open('bat', 1); });
|
|
afterEach(async () => { await engine.close(); });
|
|
|
|
it('事务 commit', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, n: { type: 'string' } }));
|
|
await engine.beginTransaction();
|
|
await engine.insert('t', [{ id: '1', n: 'a' }, { id: '2', n: 'b' }]);
|
|
await engine.commitTransaction();
|
|
expect(await engine.count('t')).toBe(2);
|
|
});
|
|
it('事务 rollback', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, n: { type: 'string' } }));
|
|
await engine.insert('t', [{ id: '1', n: 'a' }]);
|
|
await engine.beginTransaction();
|
|
await engine.insert('t', [{ id: '2', n: 'b' }]);
|
|
await engine.rollbackTransaction();
|
|
expect(await engine.count('t')).toBe(1);
|
|
});
|
|
it('insert 空数组', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
const pks = await engine.insert('t', []);
|
|
expect(pks).toHaveLength(0);
|
|
});
|
|
it('update 无匹配', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, n: { type: 'string' } }));
|
|
await engine.insert('t', [{ id: '1', n: 'a' }]);
|
|
expect(await engine.update('t', { table: 't', where: { id: 'x' } }, { n: 'x' })).toBe(0);
|
|
});
|
|
it('delete 无匹配', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
await engine.insert('t', [{ id: '1' }]);
|
|
expect(await engine.delete('t', { table: 't', where: { id: 'x' } })).toBe(0);
|
|
});
|
|
it('count 空表', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
expect(await engine.count('t')).toBe(0);
|
|
});
|
|
it('find 空表', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
expect(await engine.find('t', { table: 't' })).toHaveLength(0);
|
|
});
|
|
it('hasTable false', async () => { expect(await engine.hasTable('nope')).toBe(false); });
|
|
it('dropTable 后 hasTable false', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
await engine.dropTable('t');
|
|
expect(await engine.hasTable('t')).toBe(false);
|
|
});
|
|
it('清空表保留结构', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
await engine.insert('t', [{ id: '1' }, { id: '2' }]);
|
|
await engine.clear('t');
|
|
expect(await engine.count('t')).toBe(0);
|
|
expect(await engine.hasTable('t')).toBe(true);
|
|
});
|
|
it('重复主键抛异常', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
await engine.insert('t', [{ id: '1' }]);
|
|
await expect(engine.insert('t', [{ id: '1' }])).rejects.toThrow('Duplicate');
|
|
});
|
|
it('类型不匹配抛异常', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, age: { type: 'number' } }));
|
|
await expect(engine.insert('t', [{ id: '1', age: 'x' as any }])).rejects.toThrow('expects number');
|
|
});
|
|
it('必填字段缺失抛异常', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, name: { type: 'string', required: true } }));
|
|
await expect(engine.insert('t', [{ id: '1' }])).rejects.toThrow('required');
|
|
});
|
|
it('MemoryBackend isOpen', async () => {
|
|
const be = new MemoryBackend();
|
|
expect(be.isOpen()).toBe(false);
|
|
await be.open('x'); expect(be.isOpen()).toBe(true);
|
|
await be.close();
|
|
});
|
|
it('引擎名称为 aria', () => { expect(engine.name).toBe('aria'); });
|
|
it('默认值 number', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, val: { type: 'number', default: 42 } }));
|
|
await engine.insert('t', [{ id: '1' }]);
|
|
expect((await engine.find('t', { table: 't' }))[0].val).toBe(42);
|
|
});
|
|
it('默认值 boolean', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, active: { type: 'boolean', default: true } }));
|
|
await engine.insert('t', [{ id: '1' }]);
|
|
expect((await engine.find('t', { table: 't' }))[0].active).toBe(true);
|
|
});
|
|
it('Schema json 往返', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, meta: { type: 'json' } }));
|
|
await engine.insert('t', [{ id: '1', meta: { a: 1, b: [2, 3] } }]);
|
|
expect((await engine.find('t', { table: 't' }))[0].meta).toEqual({ a: 1, b: [2, 3] });
|
|
});
|
|
it('date 类型存储', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, ts: { type: 'date' } }));
|
|
await engine.insert('t', [{ id: '1', ts: '2026-01-01T00:00:00.000Z' }]);
|
|
expect((await engine.find('t', { table: 't' }))[0].ts).toBe('2026-01-01T00:00:00.000Z');
|
|
});
|
|
it('重复 open 不改变状态', async () => { await engine.open('bat', 1); expect(engine.isOpen()).toBe(true); });
|
|
it('GC 调用不抛异常', () => { (engine as any).tryGC(); });
|
|
it('内存检查不抛异常', () => { (engine as any).checkMemoryBudget(); });
|
|
it('引擎状态 getTableNames', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
expect(await engine.getTableNames()).toContain('t');
|
|
});
|
|
it('getTableSchema 返回正确结构', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, name: { type: 'string' } }));
|
|
const s = await engine.getTableSchema('t');
|
|
expect(s!.columns.id.primaryKey).toBe(true);
|
|
});
|
|
it('LIMIT 0 返回空', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true } }));
|
|
await engine.insert('t', [{ id: '1' }, { id: '2' }]);
|
|
expect(await engine.find('t', { table: 't', limit: 0 })).toHaveLength(0);
|
|
});
|
|
it('ORDER BY desc', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }));
|
|
await engine.insert('t', [{ id: 'a', v: 1 }, { id: 'b', v: 3 }, { id: 'c', v: 2 }]);
|
|
const r = await engine.find('t', { table: 't', orderBy: [{ column: 'v', direction: 'desc' }] });
|
|
expect(r[0].v).toBe(3); expect(r[2].v).toBe(1);
|
|
});
|
|
it('$eq 直接值', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, n: { type: 'string' } }));
|
|
await engine.insert('t', [{ id: '1', n: 'Alice' }, { id: '2', n: 'Bob' }]);
|
|
expect(await engine.find('t', { table: 't', where: { n: 'Alice' } })).toHaveLength(1);
|
|
});
|
|
it('$gte $lte 组合', async () => {
|
|
await engine.createTable(createSchema('t', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }));
|
|
for (let i = 0; i < 10; i++) await engine.insert('t', [{ id: `${i}`, v: i * 10 }]);
|
|
expect(await engine.count('t', { table: 't', where: { v: { $gte: 20, $lte: 50 } } })).toBe(4);
|
|
});
|
|
});
|