140 lines
5.7 KiB
TypeScript
140 lines
5.7 KiB
TypeScript
/**
|
||
* v0.5.1 — CRUD 生命周期钩子真实接线测试
|
||
*
|
||
* 此前 beforeInsert/afterInsert/beforeUpdate/afterUpdate/beforeDelete/afterDelete
|
||
* 六个钩子虽在 HookName 类型与文档中宣称("14 种钩子"),但从未被任何路径触发。
|
||
* 本次验证 Table API 与 SQL query 两条路径均真实触发全部 14 个钩子。
|
||
*/
|
||
import { MetonaSqlark } from '../src/core';
|
||
|
||
async function createDb(name: string): Promise<MetonaSqlark> {
|
||
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<string, unknown>).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<string, unknown> }).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<string>();
|
||
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();
|
||
});
|
||
});
|