From d33f4ab3b0eb3fdb030b0a31f416dd3f8a1b6461 Mon Sep 17 00:00:00 2001 From: thzxx Date: Mon, 27 Jul 2026 21:34:12 +0800 Subject: [PATCH] =?UTF-8?q?test:=20OPFSEngine=2012=E9=A1=B9mock=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=20+=20Schema=E7=BA=A6=E6=9D=9F5=E9=A1=B9=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=20=E2=86=92=20543=20passed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- tests/engine/opfs.test.ts | 158 +++++++++++++++++++++++++++++++++++++ tests/table/schema.test.ts | 24 ++++++ 3 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 tests/engine/opfs.test.ts diff --git a/README.md b/README.md index fb2542a..363a414 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ version license coverage - tests + tests

> 基于 TypeScript 的**前端关系型数据库**,支持完整 SQL 查询、Query Builder 链式 API、与 **AriaEngine 自研页面式存储引擎**。 @@ -294,7 +294,7 @@ npm run typecheck # 类型检查 | 指标 | 数值 | |------|------| -| 测试用例 | 526 | +| 测试用例 | 543 | | 测试套件 | 27 | | 行覆盖率 | 91.0% | | SQL 关键字 | 33 | diff --git a/tests/engine/opfs.test.ts b/tests/engine/opfs.test.ts new file mode 100644 index 0000000..35a6106 --- /dev/null +++ b/tests/engine/opfs.test.ts @@ -0,0 +1,158 @@ +/** + * OPFSEngine 测试 — mock FileSystemDirectoryHandle,验证逻辑不卡死 + * 注:jsdom 无 OPFS API,全部使用 mock 模拟 + */ +import { OPFSEngine } from '../../src/engine/opfs'; +import { createSchema } from '../../src/table/schema'; + +// Mock navigator.storage.getDirectory + FileSystemDirectoryHandle +function mockOPFS() { + const files = new Map(); + + const dirMock = { + getDirectoryHandle: async (_name: string, _opts?: any) => dirMock as any, + getFileHandle: async (name: string, opts?: any) => { + if (opts?.create) { + return { createWritable: async () => ({ write: async (d: string) => { files.set(name, d); }, close: async () => {} }) }; + } + if (!files.has(name)) throw new Error('Not found'); + return { getFile: async () => ({ text: async () => files.get(name)!, arrayBuffer: async () => new ArrayBuffer(0) }) }; + }, + removeEntry: async (name: string) => { files.delete(name); }, + }; + + // 为 entries() 添加可迭代接口 + (dirMock as any).entries = () => ({ + [Symbol.asyncIterator]: async function* () { + for (const [k] of files) yield [k]; + }, + }); + + // jsdom 环境下 navigator 可能已存在,直接设置 storage 属性 + const nav = (globalThis as any).navigator || {}; + nav.storage = { getDirectory: async () => dirMock }; + (globalThis as any).navigator = nav; + + return { files }; +} + +const userSchema = createSchema('users', { + id: { type: 'string', primaryKey: true }, + name: { type: 'string', required: true }, + age: { type: 'number', default: 0 }, +}); + +describe('OPFSEngine', () => { + let engine: OPFSEngine; + let mocks: ReturnType; + + beforeEach(async () => { + mocks = mockOPFS(); + engine = new OPFSEngine(); + await engine.open('test-opfs', 1); + }); + + afterEach(async () => { + await engine.close(); + }); + + // ---- 生命周期 ---- + it('打开后 isOpen 返回 true', () => { + expect(engine.isOpen()).toBe(true); + }); + + it('关闭后 isOpen 返回 false', async () => { + await engine.close(); + expect(engine.isOpen()).toBe(false); + }); + + // ---- 表管理 ---- + it('创建表后 hasTable 返回 true', async () => { + await engine.createTable(userSchema); + expect(await engine.hasTable('users')).toBe(true); + }); + + it('获取表名列表', async () => { + await engine.createTable(userSchema); + const names = await engine.getTableNames(); + expect(names).toContain('users'); + }); + + it('删除表后 hasTable 返回 false', async () => { + await engine.createTable(userSchema); + await engine.dropTable('users'); + expect(await engine.hasTable('users')).toBe(false); + }); + + // ---- CRUD ---- + it('插入后立即可查询', async () => { + await engine.createTable(userSchema); + await engine.insert('users', [ + { id: '1', name: 'Alice', age: 30 }, + { id: '2', name: 'Bob', age: 25 }, + ]); + const rows = await engine.find('users', { table: 'users' }); + expect(rows).toHaveLength(2); + }); + + it('count 返回正确行数', async () => { + await engine.createTable(userSchema); + await engine.insert('users', [{ id: '1', name: 'Alice' }]); + expect(await engine.count('users')).toBe(1); + }); + + it('更新后数据变化', async () => { + await engine.createTable(userSchema); + await engine.insert('users', [{ id: '1', name: 'Alice', age: 30 }]); + await engine.update('users', { table: 'users', where: { id: '1' } }, { age: 31 }); + const rows = await engine.find('users', { table: 'users', where: { id: '1' } }); + expect(rows[0].age).toBe(31); + }); + + it('删除后 count 减少', async () => { + await engine.createTable(userSchema); + await engine.insert('users', [{ id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }]); + await engine.delete('users', { table: 'users', where: { id: '1' } }); + expect(await engine.count('users')).toBe(1); + }); + + // ---- 数据持久化(模拟重启) ---- + it('close + reopen 后数据恢复', async () => { + // 写入数据 + await engine.createTable(userSchema); + await engine.insert('users', [ + { id: '1', name: 'Alice', age: 30 }, + { id: '2', name: 'Bob', age: 25 }, + ]); + const beforeRows = await engine.find('users', { table: 'users' }); + expect(beforeRows).toHaveLength(2); + + // 关闭后重新打开(模拟重启) + await engine.close(); + engine = new OPFSEngine(); + await engine.open('test-opfs', 1); + + // 数据应恢复 + const afterRows = await engine.find('users', { table: 'users' }); + expect(afterRows).toHaveLength(2); + expect(afterRows.map((r: any) => r.name).sort()).toEqual(['Alice', 'Bob']); + }); + + // ---- 事务 ---- + it('事务 commit 后数据持久化', async () => { + await engine.createTable(userSchema); + await engine.beginTransaction(); + await engine.insert('users', [{ id: '1', name: 'Alice' }]); + await engine.commitTransaction(); + expect(await engine.count('users')).toBe(1); + }); + + it('事务 rollback 后数据恢复', async () => { + await engine.createTable(userSchema); + await engine.insert('users', [{ id: '1', name: 'Alice' }]); + await engine.beginTransaction(); + await engine.insert('users', [{ id: '2', name: 'Bob' }]); + await engine.rollbackTransaction(); + expect(await engine.count('users')).toBe(1); + }); +}); diff --git a/tests/table/schema.test.ts b/tests/table/schema.test.ts index 379101f..491e303 100644 --- a/tests/table/schema.test.ts +++ b/tests/table/schema.test.ts @@ -145,6 +145,30 @@ describe('Schema 边缘场景', () => { }); }); + // ---- 约束校验(v0.2.3) ---- + describe('约束校验', () => { + it('string 超出 maxLength 抛出错误', () => { + expect(() => checkFieldType('t', 'c', 'string', 'hello', { type: 'string', maxLength: 3 })) + .toThrow('exceeds max length'); + }); + it('string 未超 maxLength 不报错', () => { + expect(() => checkFieldType('t', 'c', 'string', 'hi', { type: 'string', maxLength: 5 })) + .not.toThrow(); + }); + it('number 低于 min 抛出错误', () => { + expect(() => checkFieldType('t', 'c', 'number', -5, { type: 'number', min: 0 })) + .toThrow('below minimum'); + }); + it('number 高于 max 抛出错误', () => { + expect(() => checkFieldType('t', 'c', 'number', 200, { type: 'number', max: 100 })) + .toThrow('above maximum'); + }); + it('number 在范围内不报错', () => { + expect(() => checkFieldType('t', 'c', 'number', 50, { type: 'number', min: 0, max: 100 })) + .not.toThrow(); + }); + }); + // ---- astColumnToColumnDef ---- describe('astColumnToColumnDef', () => {