/** * v0.5.1 — CRUD 生命周期钩子真实接线测试 * * 此前 beforeInsert/afterInsert/beforeUpdate/afterUpdate/beforeDelete/afterDelete * 六个钩子虽在 HookName 类型与文档中宣称("14 种钩子"),但从未被任何路径触发。 * 本次验证 Table API 与 SQL query 两条路径均真实触发全部 14 个钩子。 */ import { MetonaSqlark } from '../src/core'; import 'fake-indexeddb/auto'; async function createDb(name: string): Promise { const db = new MetonaSqlark({ name: `hooks-${name}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`, mode: 'memory' }); await db.init(); await db.defineTable('users', { id: { type: 'string', primaryKey: true }, name: { type: 'string' }, age: { type: 'number' }, }); return db; } describe('v0.5.1 — CRUD hooks 真实接线', () => { it('Table.insert / insertMany 触发 beforeInsert + afterInsert', async () => { const db = await createDb('table-insert'); const events: string[] = []; db.on('beforeInsert', (rows) => { events.push(`before:${(rows as unknown[]).length}`); }); db.on('afterInsert', (rows, pks) => { events.push(`after:${(rows as unknown[]).length}:${(pks as string[]).length}`); }); await db.table('users').insert({ id: '1', name: 'A' }); await db.table('users').insertMany([{ id: '2', name: 'B' }, { id: '3', name: 'C' }]); expect(events).toEqual([ 'before:1', 'after:1:1', 'before:2', 'after:2:2', ]); await db.close(); }); it('Table.update().execute() 触发 beforeUpdate + afterUpdate(含影响行数)', async () => { const db = await createDb('table-update'); await db.table('users').insertMany([{ id: '1', name: 'A', age: 1 }, { id: '2', name: 'B', age: 2 }]); const events: string[] = []; db.on('beforeUpdate', (query, updates) => { events.push(`before:${(updates as Record).name}`); }); db.on('afterUpdate', (_query, _updates, count) => { events.push(`after:${count}`); }); await db.table('users').update({ name: 'X' }).where({ age: { $gt: 1 } }).execute(); expect(events).toEqual(['before:X', 'after:1']); await db.close(); }); it('Table.delete().execute() 触发 beforeDelete + afterDelete(含影响行数)', async () => { const db = await createDb('table-delete'); await db.table('users').insertMany([{ id: '1', name: 'A' }, { id: '2', name: 'B' }]); const events: string[] = []; db.on('beforeDelete', (query) => { events.push(`before:${(query as { where: Record }).where.id}`); }); db.on('afterDelete', (_query, count) => { events.push(`after:${count}`); }); await db.table('users').delete().where({ id: '1' }).execute(); expect(events).toEqual(['before:1', 'after:1']); await db.close(); }); it('SQL query 路径触发全部 CRUD hooks(INSERT/UPDATE/DELETE)', async () => { const db = await createDb('sql-crud'); await db.query("INSERT INTO users VALUES ('1', 'Alice', 30)"); const events: string[] = []; db.on('beforeInsert', () => events.push('beforeInsert')); db.on('afterInsert', () => events.push('afterInsert')); db.on('beforeUpdate', () => events.push('beforeUpdate')); db.on('afterUpdate', () => events.push('afterUpdate')); db.on('beforeDelete', () => events.push('beforeDelete')); db.on('afterDelete', () => events.push('afterDelete')); await db.query("INSERT INTO users VALUES ('2', 'Bob', 25)"); await db.query("UPDATE users SET age = 26 WHERE id = '2'"); await db.query("DELETE FROM users WHERE id = '1'"); expect(events).toEqual([ 'beforeInsert', 'afterInsert', 'beforeUpdate', 'afterUpdate', 'beforeDelete', 'afterDelete', ]); await db.close(); }); it('SQL INSERT 多行语句按列名映射触发 afterInsert', async () => { const db = await createDb('sql-insert-cols'); const seen: unknown[] = []; db.on('afterInsert', (_rows, pks) => seen.push(pks)); await db.query("INSERT INTO users (id, name) VALUES ('9', 'Zed')"); expect(seen).toEqual([['9']]); await db.close(); }); it('全部 14 个钩子均可注册并真实触发', async () => { const db = await createDb('all-hooks'); const fired = new Set(); const hooks = [ 'beforeCreateTable', 'afterCreateTable', 'beforeDropTable', 'afterDropTable', 'beforeInsert', 'afterInsert', 'beforeUpdate', 'afterUpdate', 'beforeDelete', 'afterDelete', 'beforeQuery', 'afterQuery', 'beforeTransaction', 'afterTransaction', ] as const; for (const h of hooks) db.on(h, () => fired.add(h)); await db.defineTable('t2', { id: { type: 'string', primaryKey: true } }); // createTable hooks await db.table('t2').insert({ id: '1' }); // insert hooks await db.table('t2').update({}).where({ id: '1' }).execute(); // update hooks await db.table('t2').delete().where({ id: '1' }).execute(); // delete hooks await db.query('SELECT * FROM t2'); // query hooks await db.transaction(async () => { /* noop */ }); // transaction hooks await db.dropTable('t2'); // dropTable hooks for (const h of hooks) { expect(fired.has(h)).toBe(true); if (!fired.has(h)) throw new Error(`hook ${h} 应被触发`); } await db.close(); }); it('事务路径不触发 CRUD hooks(Table 无 hooks 回调)', async () => { const db = await createDb('tx-hooks'); let fired = false; db.on('beforeInsert', () => { fired = true; }); await db.transaction(async (trx) => { await trx.table('users').insert({ id: '1', name: 'Tx' }); }); expect(fired).toBe(false); await db.close(); }); });