Files
MetonaSqlark/tests/engine/aria.test.ts
T
thzxx 0dba1abf2a 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
2026-09-14 21:03:06 +08:00

936 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AriaEngine 完整测试套件 (v0.2.0)
*
* 覆盖:
* 生命周期 · 表管理 · CRUD · 事务 · 持久化 · 查询 · 边界
*/
import { AriaEngine } from '../../src/engine/aria/index';
import { createSchema } from '../../src/table/schema';
import { MetonaSqlark } from '../../src/core';
import { resetOPFSMock } from '../helpers/storage-harness';
beforeEach(() => { resetOPFSMock(); });
// ===================================================================
// AriaEngine 引擎级测试 (Memory Backend)
// ===================================================================
describe('AriaEngine — Memory Backend', () => {
let engine: AriaEngine;
const userSchema = createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
age: { type: 'number', default: 0 },
email: { type: 'string', unique: true },
active: { type: 'boolean', default: true },
});
beforeEach(async () => {
engine = new AriaEngine({ storageBackend: 'memory' });
await engine.open('test-aria', 1);
});
afterEach(async () => {
await engine.close();
});
// ---- 生命周期 ----
describe('生命周期', () => {
it('打开后 isOpen 返回 true', () => {
expect(engine.isOpen()).toBe(true);
});
it('关闭后 isOpen 返回 false', async () => {
await engine.close();
expect(engine.isOpen()).toBe(false);
});
it('重复 open 不报错(幂等)', async () => {
await engine.open('test-aria', 1);
expect(engine.isOpen()).toBe(true);
});
it('未打开时操作抛出错误', () => {
const e = new AriaEngine({ storageBackend: 'memory' });
return expect(e.createTable(userSchema)).rejects.toThrow('not opened');
});
});
// ---- 表管理 ----
describe('表管理', () => {
it('创建表', async () => {
await engine.createTable(userSchema);
expect(await engine.hasTable('users')).toBe(true);
});
it('重复创建表抛出错误', async () => {
await engine.createTable(userSchema);
await expect(engine.createTable(userSchema)).rejects.toThrow('already exists');
});
it('获取所有表名', async () => {
await engine.createTable(userSchema);
const names = await engine.getTableNames();
expect(names).toContain('users');
});
it('获取表结构', async () => {
await engine.createTable(userSchema);
const schema = await engine.getTableSchema('users');
expect(schema).not.toBeNull();
expect(schema!.name).toBe('users');
expect(schema!.columns.id.primaryKey).toBe(true);
});
it('获取不存在表的 schema 返回 null', async () => {
expect(await engine.getTableSchema('nonexistent')).toBeNull();
});
it('删除表', async () => {
await engine.createTable(userSchema);
await engine.dropTable('users');
expect(await engine.hasTable('users')).toBe(false);
});
it('hasTable 返回 false', async () => {
expect(await engine.hasTable('nope')).toBe(false);
});
});
// ---- 插入 ----
describe('插入', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
});
it('插入单行返回主键', async () => {
const pks = await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'alice@test.com' },
]);
expect(pks).toEqual(['1']);
});
it('插入多行', async () => {
const pks = await engine.insert('users', [
{ id: '1', name: 'Alice', email: 'a@t.com' },
{ id: '2', name: 'Bob', email: 'b@t.com' },
]);
expect(pks).toEqual(['1', '2']);
});
it('重复主键抛出错误', async () => {
await engine.insert('users', [{ id: '1', name: 'Alice', email: 'a@t.com' }]);
await expect(
engine.insert('users', [{ id: '1', name: 'Dup', email: 'd@t.com' }]),
).rejects.toThrow('Duplicate');
});
it('必填字段缺失抛出错误', async () => {
await expect(
engine.insert('users', [{ id: '1' }]),
).rejects.toThrow('required');
});
it('默认值填充', async () => {
await engine.insert('users', [{ id: '1', name: 'Alice', email: 'a@t.com' }]);
const rows = await engine.find('users', { table: 'users' });
expect(rows[0].age).toBe(0);
expect(rows[0].active).toBe(true);
});
it('类型错误抛出异常', async () => {
await expect(
engine.insert('users', [{ id: '1', name: 'Alice', age: 'not-a-number' as any, email: 'a@t.com' }]),
).rejects.toThrow('expects number');
});
it('插入不存在的表抛出错误', async () => {
await expect(
engine.insert('ghosts', [{ id: '1' }]),
).rejects.toThrow('does not exist');
});
});
// ---- 查询 ----
describe('查询', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'alice@test.com' },
{ id: '2', name: 'Bob', age: 25, email: 'bob@test.com' },
{ id: '3', name: 'Charlie', age: 35, email: 'charlie@test.com' },
]);
});
it('查询所有行', async () => {
const rows = await engine.find('users', { table: 'users' });
expect(rows).toHaveLength(3);
});
it('WHERE $gt', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $gt: 28 } },
});
expect(rows).toHaveLength(2);
expect(rows.map((r) => r.id).sort()).toEqual(['1', '3']);
});
it('WHERE $eq(等值)', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { name: 'Alice' },
});
expect(rows).toHaveLength(1);
expect(rows[0].id).toBe('1');
});
it('WHERE $in', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $in: [25, 35] } },
});
expect(rows).toHaveLength(2);
});
it('WHERE $like', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { name: { $like: 'A%' } },
});
expect(rows).toHaveLength(1);
expect(rows[0].name).toBe('Alice');
});
it('WHERE $and', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { $and: [{ age: { $gt: 20 } }, { age: { $lt: 35 } }] },
});
expect(rows).toHaveLength(2);
});
it('WHERE $or', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { $or: [{ name: 'Alice' }, { name: 'Charlie' }] },
});
expect(rows).toHaveLength(2);
});
it('WHERE $ne', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $ne: 30 } },
});
expect(rows).toHaveLength(2);
});
it('WHERE $gte + $lte', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $gte: 25, $lte: 30 } },
});
expect(rows).toHaveLength(2);
});
it('ORDER BY asc', async () => {
const rows = await engine.find('users', {
table: 'users',
orderBy: [{ column: 'age', direction: 'asc' }],
});
expect(rows.map((r) => r.age)).toEqual([25, 30, 35]);
});
it('ORDER BY desc', async () => {
const rows = await engine.find('users', {
table: 'users',
orderBy: [{ column: 'age', direction: 'desc' }],
});
expect(rows.map((r) => r.age)).toEqual([35, 30, 25]);
});
it('LIMIT', async () => {
const rows = await engine.find('users', {
table: 'users',
limit: 2,
});
expect(rows).toHaveLength(2);
});
it('OFFSET + LIMIT', async () => {
const rows = await engine.find('users', {
table: 'users',
orderBy: [{ column: 'id', direction: 'asc' }],
offset: 1,
limit: 1,
});
expect(rows).toHaveLength(1);
expect(rows[0].id).toBe('2');
});
it('列投影', async () => {
const rows = await engine.find('users', {
table: 'users',
columns: ['id', 'name'],
where: { id: '1' },
});
expect(Object.keys(rows[0]).sort()).toEqual(['id', 'name']);
});
it('空结果查询', async () => {
const rows = await engine.find('users', {
table: 'users',
where: { age: { $gt: 999 } },
});
expect(rows).toHaveLength(0);
});
});
// ---- 更新 ----
describe('更新', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'alice@test.com' },
{ id: '2', name: 'Bob', age: 25, email: 'bob@test.com' },
]);
});
it('条件更新', async () => {
const count = await engine.update('users',
{ table: 'users', where: { id: '1' } },
{ age: 31 },
);
expect(count).toBe(1);
const rows = await engine.find('users', { table: 'users', where: { id: '1' } });
expect(rows[0].age).toBe(31);
});
it('更新所有行(无 where', async () => {
const count = await engine.update('users',
{ table: 'users' },
{ age: 100 },
);
expect(count).toBe(2);
});
it('更新不存在的表抛出错误', async () => {
await expect(
engine.update('ghosts', { table: 'ghosts' }, { x: 1 }),
).rejects.toThrow();
});
});
// ---- 删除 ----
describe('删除', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'alice@test.com' },
{ id: '2', name: 'Bob', age: 25, email: 'bob@test.com' },
]);
});
it('条件删除', async () => {
const count = await engine.delete('users', { table: 'users', where: { id: '1' } });
expect(count).toBe(1);
expect(await engine.count('users')).toBe(1);
});
it('删除所有行(无 where', async () => {
const count = await engine.delete('users', { table: 'users' });
expect(count).toBe(2);
expect(await engine.count('users')).toBe(0);
});
it('清空表', async () => {
await engine.clear('users');
expect(await engine.count('users')).toBe(0);
});
});
// ---- Count ----
describe('Count', () => {
beforeEach(async () => {
await engine.createTable(userSchema);
await engine.insert('users', [
{ id: '1', name: 'Alice', age: 30, email: 'alice@test.com' },
{ id: '2', name: 'Bob', age: 25, email: 'bob@test.com' },
]);
});
it('count 全部', async () => {
expect(await engine.count('users')).toBe(2);
});
it('count with where', async () => {
expect(await engine.count('users', { table: 'users', where: { age: { $gt: 27 } } })).toBe(1);
});
});
});
// ===================================================================
// AriaEngine 持久化测试 (Memory Backend, within-session)
// ===================================================================
describe('AriaEngine — 持久化 (Memory Backend)', () => {
it('创建表 → 不关闭 → 多次操作后数据一致', async () => {
const e = new AriaEngine({ storageBackend: 'memory' });
await e.open('test-persist-1', 1);
await e.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
}));
await e.insert('users', [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
]);
// 多次查询验证
expect(await e.count('users')).toBe(2);
expect(await e.count('users')).toBe(2);
await e.close();
});
it('CRUD 操作 → count 验证', async () => {
const e = new AriaEngine({ storageBackend: 'memory' });
await e.open('test-persist-2', 1);
await e.createTable(createSchema('items', {
id: { type: 'string', primaryKey: true },
val: { type: 'number', default: 0 },
}));
await e.insert('items', [
{ id: 'a', val: 1 },
{ id: 'b', val: 2 },
{ id: 'c', val: 3 },
]);
await e.update('items', { table: 'items', where: { id: 'b' } }, { val: 20 });
await e.delete('items', { table: 'items', where: { id: 'c' } });
const rows = await e.find('items', { table: 'items', orderBy: [{ column: 'id', direction: 'asc' }] });
expect(rows).toHaveLength(2);
expect(rows[0]).toMatchObject({ id: 'a', val: 1 });
expect(rows[1]).toMatchObject({ id: 'b', val: 20 });
await e.close();
});
it('多表操作', async () => {
const e = new AriaEngine({ storageBackend: 'memory' });
await e.open('test-persist-3', 1);
await e.createTable(createSchema('t1', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }));
await e.createTable(createSchema('t2', { id: { type: 'string', primaryKey: true }, v: { type: 'number' } }));
await e.insert('t1', [{ id: 'x', v: 1 }]);
await e.insert('t2', [{ id: 'y', v: 2 }]);
const names = await e.getTableNames();
expect(names.sort()).toEqual(['t1', 't2']);
expect(await e.count('t1')).toBe(1);
expect(await e.count('t2')).toBe(1);
await e.close();
});
});
// ===================================================================
// AriaEngine Schema/数据持久化测试 (Memory Backend, 安全无 IDB)
// ===================================================================
// 注:原 IndexedDB 持久化测试使用 fake-indexeddb 会导致 CI 卡死。
// 改为使用 Memory Backend 验证持久化流程:Schema 写入 → 读取 → 数据保持。
describe('AriaEngine — Schema 持久化 (Memory Backend)', () => {
it('Schema 持久化写入后再读取保持一致', async () => {
const engine = new AriaEngine({ storageBackend: 'memory' });
await engine.open('test-schema-persist', 1);
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
}));
// 直接通过 backend 验证 Schema JSON 已写入
const raw = await (engine as any).backend.read('__aria_schemas');
expect(raw).not.toBeNull();
const json = new TextDecoder().decode(raw);
const data = JSON.parse(json);
expect(data.users).toBeDefined();
expect(data.users.id.primaryKey).toBe(true);
await engine.close();
});
it('close 后 Schema 可重新 load', async () => {
const engine1 = new AriaEngine({ storageBackend: 'memory' });
await engine1.open('test-reload', 1);
await engine1.createTable(createSchema('items', {
id: { type: 'string', primaryKey: true },
val: { type: 'number', default: 0 },
}));
// 数据写入后 close(触发 schema 持久化 + flush
await engine1.insert('items', [{ id: 'a', val: 1 }, { id: 'b', val: 2 }]);
await engine1.close();
// 重新 open 并验证数据仍在(Memory Backend 的 close 会清空,但验证 loadSchemas 流程)
const engine2 = new AriaEngine({ storageBackend: 'memory' });
await engine2.open('test-reload', 1);
// Memory backend close 会清空 store,所以数据不保留
// 但我们可以验证引擎能正常重新初始化
expect(engine2.isOpen()).toBe(true);
await engine2.close();
});
});
// ===================================================================
// AriaEngine 事务测试
// ===================================================================
describe('AriaEngine — 事务', () => {
let engine: AriaEngine;
beforeEach(async () => {
engine = new AriaEngine({ storageBackend: 'memory' });
await engine.open('test-aria-tx', 1);
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
balance: { type: 'number', default: 0 },
}));
});
afterEach(async () => {
await engine.close();
});
it('begin + commit: 事务中插入的数据最终可见', async () => {
await engine.beginTransaction();
await engine.insert('users', [{ id: '1', name: 'Alice', balance: 100 }]);
await engine.insert('users', [{ id: '2', name: 'Bob', balance: 200 }]);
await engine.commitTransaction();
expect(await engine.count('users')).toBe(2);
});
it('rollback: 事务中的数据不被持久化', async () => {
await engine.insert('users', [{ id: '1', name: 'Alice', balance: 100 }]);
await engine.beginTransaction();
await engine.insert('users', [{ id: '2', name: 'Bob', balance: 200 }]);
await engine.rollbackTransaction();
expect(await engine.count('users')).toBe(1);
const rows = await engine.find('users', { table: 'users' });
expect(rows[0].name).toBe('Alice');
});
it('双重 beginTransaction 抛出错误', async () => {
await engine.beginTransaction();
await expect(engine.beginTransaction()).rejects.toThrow('already in progress');
await engine.rollbackTransaction();
});
it('未开始事务时 commit 抛出错误', async () => {
await expect(engine.commitTransaction()).rejects.toThrow('No active transaction');
});
it('未开始事务时 rollback 抛出错误', async () => {
await expect(engine.rollbackTransaction()).rejects.toThrow('No active transaction');
});
});
// ===================================================================
// AriaEngine 通过 MetonaSqlark (mode: 'aria') 集成测试
// ===================================================================
describe('MetonaSqlark with mode=aria (Memory)', () => {
let db: MetonaSqlark;
beforeEach(async () => {
db = new MetonaSqlark({ name: `ms-aria-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, mode: 'aria', diskEngine: 'opfs' });
await db.init();
});
afterEach(async () => {
await db.close();
});
it('创建数据库并初始化', () => {
expect(db.isReady()).toBe(true);
expect(db.mode).toBe('aria');
});
it('定义表 + 基本 CRUD', async () => {
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
age: { type: 'number', default: 0 },
});
const table = db.table('users');
await table.insert({ id: '1', name: 'Alice', age: 30 });
await table.insert({ id: '2', name: 'Bob', age: 25 });
expect(await table.count()).toBe(2);
const rows = await table.select().where({ age: { $gt: 20 } }).execute();
expect(rows).toHaveLength(2);
});
it('SQL INSERT + SELECT', async () => {
await db.query('CREATE TABLE users (id STRING PRIMARY KEY, name STRING, age NUMBER)');
await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)");
const result = await db.query('SELECT * FROM users') as Record<string, unknown>[];
expect(result).toHaveLength(1);
expect(result[0].name).toBe('Alice');
});
it('SQL UPDATE + DELETE', async () => {
await db.query('CREATE TABLE users (id STRING PRIMARY KEY, name STRING)');
await db.query("INSERT INTO users VALUES ('1', 'Alice')");
await db.query("UPDATE users SET name = 'Alicia' WHERE id = '1'");
const rows = await db.query("SELECT * FROM users WHERE id = '1'") as Record<string, unknown>[];
expect(rows[0].name).toBe('Alicia');
await db.query("DELETE FROM users WHERE id = '1'");
expect(await db.table('users').count()).toBe(0);
});
it('SQL ORDER BY + LIMIT', async () => {
await db.query('CREATE TABLE users (id STRING PRIMARY KEY, name STRING, age NUMBER)');
await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)");
await db.query("INSERT INTO users VALUES ('2', 'Bob', 25)");
await db.query("INSERT INTO users VALUES ('3', 'Charlie', 35)");
const result = await db.query('SELECT * FROM users ORDER BY age DESC LIMIT 2') as Record<string, unknown>[];
expect(result).toHaveLength(2);
expect(result[0].name).toBe('Charlie');
expect(result[1].name).toBe('Alice');
});
it('SQL GROUP BY + 聚合', async () => {
await db.query('CREATE TABLE emp (id STRING PRIMARY KEY, dept STRING, salary NUMBER)');
await db.query("INSERT INTO emp VALUES ('1', 'Eng', 1000)");
await db.query("INSERT INTO emp VALUES ('2', 'Eng', 1200)");
await db.query("INSERT INTO emp VALUES ('3', 'Sales', 900)");
const result = await db.query(
'SELECT dept, SUM(salary) AS total FROM emp GROUP BY dept',
) as Record<string, unknown>[];
expect(result).toHaveLength(2);
});
it('导出生效', async () => {
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
});
await db.table('users').insert({ id: '1', name: 'Alice' });
const exported = await db.exportTable('users');
expect(exported).toHaveLength(1);
expect(exported[0].name).toBe('Alice');
});
});
// ===================================================================
// AriaEngine 边界与错误处理测试
// ===================================================================
describe('AriaEngine — 边界与错误处理', () => {
let engine: AriaEngine;
beforeEach(async () => {
engine = new AriaEngine({ storageBackend: 'memory' });
await engine.open('test-edge', 1);
});
afterEach(async () => {
await engine.close();
});
it('操作不存在的表抛出 TABLE_NOT_FOUND', async () => {
await expect(engine.find('ghosts', { table: 'ghosts' })).rejects.toThrow('does not exist');
await expect(engine.insert('ghosts', [{ id: '1' }])).rejects.toThrow('does not exist');
await expect(engine.update('ghosts', { table: 'ghosts' }, {})).rejects.toThrow('does not exist');
await expect(engine.delete('ghosts', { table: 'ghosts' })).rejects.toThrow('does not exist');
});
it('dropTable 删除不存在的表抛出错误', async () => {
await expect(engine.dropTable('nope')).rejects.toThrow('does not exist');
});
it('close 后操作抛出错误', async () => {
await engine.close();
await expect(engine.find('users', { table: 'users' })).rejects.toThrow('not opened');
});
it('空表 count 返回 0', async () => {
await engine.createTable(createSchema('empty', {
id: { type: 'string', primaryKey: true },
v: { type: 'number' },
}));
expect(await engine.count('empty')).toBe(0);
});
it('空表 find 返回空数组', async () => {
await engine.createTable(createSchema('empty', {
id: { type: 'string', primaryKey: true },
}));
const rows = await engine.find('empty', { table: 'empty' });
expect(rows).toHaveLength(0);
});
it('clear 空表不报错', async () => {
await engine.createTable(createSchema('empty', {
id: { type: 'string', primaryKey: true },
}));
await engine.clear('empty');
expect(await engine.count('empty')).toBe(0);
});
it('update 无匹配行返回 0', async () => {
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string' },
}));
await engine.insert('users', [{ id: '1', name: 'Alice' }]);
const cnt = await engine.update('users', { table: 'users', where: { id: 'x' } }, { name: 'X' });
expect(cnt).toBe(0);
});
it('delete 无匹配行返回 0', async () => {
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
}));
await engine.insert('users', [{ id: '1' }]);
const cnt = await engine.delete('users', { table: 'users', where: { id: 'x' } });
expect(cnt).toBe(0);
});
it('大量数据插入与查询 (100 行)', async () => {
await engine.createTable(createSchema('big', {
id: { type: 'string', primaryKey: true },
val: { type: 'number' },
}));
const rows = [];
for (let i = 0; i < 100; i++) {
rows.push({ id: `${i}`, val: i * 10 });
}
await engine.insert('big', rows);
expect(await engine.count('big')).toBe(100);
const result = await engine.find('big', {
table: 'big',
orderBy: [{ column: 'val', direction: 'asc' }],
limit: 5,
});
expect(result).toHaveLength(5);
expect(result[0].val).toBe(0);
});
it('多条件 WHERE 组合', async () => {
await engine.createTable(createSchema('items', {
id: { type: 'string', primaryKey: true },
cat: { type: 'string' },
price: { type: 'number' },
}));
await engine.insert('items', [
{ id: '1', cat: 'A', price: 10 },
{ id: '2', cat: 'A', price: 20 },
{ id: '3', cat: 'B', price: 30 },
{ id: '4', cat: 'A', price: 40 },
]);
const rows = await engine.find('items', {
table: 'items',
where: { $and: [{ cat: 'A' }, { price: { $gt: 15 } }] },
});
expect(rows).toHaveLength(2);
});
it('WHERE $not 条件', async () => {
await engine.createTable(createSchema('items', {
id: { type: 'string', primaryKey: true },
val: { type: 'number' },
}));
for (let i = 0; i < 5; i++) {
await engine.insert('items', [{ id: `${i}`, val: i }]);
}
const rows = await engine.find('items', {
table: 'items',
where: { val: { $not: { $eq: 3 } } },
});
expect(rows).toHaveLength(4);
});
it('count with $or', async () => {
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
age: { type: 'number' },
}));
await engine.insert('users', [
{ id: '1', age: 20 },
{ id: '2', age: 25 },
{ id: '3', age: 30 },
]);
expect(await engine.count('users', {
table: 'users',
where: { $or: [{ age: 20 }, { age: 30 }] },
})).toBe(2);
});
it('主键索引加速 — $eq 查询', async () => {
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string' },
}));
for (let i = 0; i < 50; i++) {
await engine.insert('users', [{ id: `${i}`, name: `User${i}` }]);
}
// PK 等值查询应直接通过索引
const rows = await engine.find('users', {
table: 'users',
where: { id: '25' },
});
expect(rows).toHaveLength(1);
expect(rows[0].name).toBe('User25');
});
it('多列 ORDER BY', async () => {
await engine.createTable(createSchema('items', {
id: { type: 'string', primaryKey: true },
cat: { type: 'string' },
price: { type: 'number' },
}));
await engine.insert('items', [
{ id: '1', cat: 'A', price: 30 },
{ id: '2', cat: 'B', price: 10 },
{ id: '3', cat: 'A', price: 20 },
]);
const rows = await engine.find('items', {
table: 'items',
orderBy: [{ column: 'cat', direction: 'asc' }, { column: 'price', direction: 'asc' }],
});
expect(rows[0]).toMatchObject({ cat: 'A', price: 20 });
expect(rows[1]).toMatchObject({ cat: 'A', price: 30 });
expect(rows[2]).toMatchObject({ cat: 'B', price: 10 });
});
it('列投影 — 跨表格式列名', async () => {
await engine.createTable(createSchema('test', {
id: { type: 'string', primaryKey: true },
a: { type: 'number' },
b: { type: 'number' },
c: { type: 'number' },
}));
await engine.insert('test', [{ id: '1', a: 1, b: 2, c: 3 }]);
const rows = await engine.find('test', {
table: 'test',
columns: ['a', 'c'],
});
expect(Object.keys(rows[0])).toEqual(['a', 'c']);
expect(rows[0].a).toBe(1);
expect(rows[0].c).toBe(3);
});
it('boolean 类型正确存储和查询', async () => {
await engine.createTable(createSchema('flags', {
id: { type: 'string', primaryKey: true },
active: { type: 'boolean' },
}));
await engine.insert('flags', [
{ id: '1', active: true },
{ id: '2', active: false },
]);
const active = await engine.find('flags', { table: 'flags', where: { active: true } });
expect(active).toHaveLength(1);
expect(active[0].id).toBe('1');
});
it('date 类型存储和显示', async () => {
await engine.createTable(createSchema('events', {
id: { type: 'string', primaryKey: true },
at: { type: 'date' },
}));
const ts = '2026-01-01T00:00:00.000Z';
await engine.insert('events', [{ id: 'e1', at: ts }]);
const rows = await engine.find('events', { table: 'events' });
expect(rows[0].at).toBe(ts);
});
it('json 类型存储', async () => {
await engine.createTable(createSchema('docs', {
id: { type: 'string', primaryKey: true },
meta: { type: 'json' },
}));
await engine.insert('docs', [{ id: 'd1', meta: { tags: ['a', 'b'], count: 5 } }]);
const rows = await engine.find('docs', { table: 'docs' });
expect(rows[0].meta).toEqual({ tags: ['a', 'b'], count: 5 });
});
it('$like 模糊查询', async () => {
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string' },
}));
await engine.insert('users', [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Alicia' },
{ id: '3', name: 'Bob' },
]);
const rows = await engine.find('users', {
table: 'users',
where: { name: { $like: 'Ali%' } },
});
expect(rows).toHaveLength(2);
});
it('$in 查询', async () => {
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string' },
}));
await engine.insert('users', [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
{ id: '3', name: 'Charlie' },
]);
const rows = await engine.find('users', {
table: 'users',
where: { name: { $in: ['Alice', 'Charlie'] } },
});
expect(rows).toHaveLength(2);
});
it('dropTable 后重新创建同名表', async () => {
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string' },
}));
await engine.dropTable('users');
await engine.createTable(createSchema('users', {
id: { type: 'string', primaryKey: true },
v: { type: 'number' },
}));
const schema = await engine.getTableSchema('users');
expect(schema!.columns.v).toBeDefined();
expect(schema!.columns.name).toBeUndefined();
});
it('insert 后可立即 find 同一行', async () => {
await engine.createTable(createSchema('test', {
id: { type: 'string', primaryKey: true },
v: { type: 'number' },
}));
await engine.insert('test', [{ id: '1', v: 42 }]);
const rows = await engine.find('test', { table: 'test', where: { id: '1' } });
expect(rows[0].v).toBe(42);
});
});