Files
MetonaSqlark/tests/transaction-rollback.test.ts
T
thzxx 71e238a5cc
CI / test (18.x) (push) Successful in 9m52s
CI / test (20.x) (push) Successful in 9m50s
CI / test (22.x) (push) Successful in 9m51s
CI / test (24.x) (push) Successful in 9m49s
test: v0.1.13 新增42项测试覆盖(事务回滚/子查询/外键级联/连接池)
2026-07-26 16:40:11 +08:00

214 lines
7.0 KiB
TypeScript

/**
* v0.1.13 事务回滚测试
* 覆盖 Memory / IndexedDB / Hybrid 三种引擎的 begin/commit/rollback
*/
import { MetonaSqlark } from '../src/core';
import 'fake-indexeddb/auto';
let idbCounter = 0;
// ===================================================================
// Memory 引擎事务回滚
// ===================================================================
describe('v0.1.13 事务回滚 — Memory 模式', () => {
let db: MetonaSqlark;
beforeEach(async () => {
db = new MetonaSqlark({ name: 'tx-test-mem', mode: 'memory' });
await db.init();
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
balance: { type: 'number', default: 0 },
});
});
afterEach(async () => { await db.close(); });
it('commit: 事务成功后数据持久化', async () => {
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '1', name: 'Alice', balance: 100 });
await trx.table('users').insert({ id: '2', name: 'Bob', balance: 200 });
});
const rows = await db.table('users').select().execute();
expect(rows).toHaveLength(2);
});
it('rollback: 事务失败后数据恢复', async () => {
// 先插入一条
await db.table('users').insert({ id: '1', name: 'Alice', balance: 100 });
try {
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '2', name: 'Bob', balance: 200 });
// 故意触发重复主键错误
await trx.table('users').insert({ id: '2', name: 'Duplicate', balance: 0 });
});
} catch { /* expected */ }
// 回滚后:只有最初的那条数据
const rows = await db.table('users').select().execute();
expect(rows).toHaveLength(1);
expect(rows[0].name).toBe('Alice');
});
it('rollback: 更新操作回滚', async () => {
await db.table('users').insert({ id: '1', name: 'Alice', balance: 100 });
try {
await db.transaction(async (trx) => {
await trx.table('users').update({ balance: 999 }).where({ id: '1' }).execute();
throw new Error('手动失败');
});
} catch { /* expected */ }
const rows = await db.table('users').select().execute();
expect(rows[0].balance).toBe(100); // 回滚到 100
});
it('rollback: 删除操作回滚', async () => {
await db.table('users').insert({ id: '1', name: 'Alice', balance: 100 });
await db.table('users').insert({ id: '2', name: 'Bob', balance: 200 });
try {
await db.transaction(async (trx) => {
await trx.table('users').delete().where({ id: '1' }).execute();
throw new Error('手动失败');
});
} catch { /* expected */ }
const rows = await db.table('users').select().execute();
expect(rows).toHaveLength(2); // 两条都在
});
it('rollback: 混合操作回滚', async () => {
await db.table('users').insert({ id: '1', name: 'Alice', balance: 100 });
await db.table('users').insert({ id: '2', name: 'Bob', balance: 200 });
try {
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '3', name: 'Charlie', balance: 300 });
await trx.table('users').update({ balance: 0 }).where({ id: '1' }).execute();
await trx.table('users').delete().where({ id: '2' }).execute();
throw new Error('手动失败');
});
} catch { /* expected */ }
const rows = await db.table('users').select().execute();
expect(rows).toHaveLength(2);
expect(rows.find((r: any) => r.id === '1')!.balance).toBe(100);
expect(rows.find((r: any) => r.id === '2')).toBeTruthy();
});
it('事务返回结果', async () => {
const result = await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '1', name: 'Alice' });
return 'OK';
});
expect(result).toBe('OK');
expect(await db.table('users').count()).toBe(1);
});
it('事务错误抛出 DatabaseError', async () => {
await expect(db.transaction(async () => {
throw new Error('custom error');
})).rejects.toThrow('Transaction failed');
});
});
// ===================================================================
// IndexedDB 引擎事务回滚
// ===================================================================
describe('v0.1.13 事务回滚 — Disk 模式 (IndexedDB)', () => {
let db: MetonaSqlark;
let dbName: string;
beforeEach(async () => {
dbName = `tx-disk-${++idbCounter}`;
db = new MetonaSqlark({ name: dbName, mode: 'disk', diskEngine: 'indexeddb' });
await db.init();
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
});
});
afterEach(async () => {
await db.close();
try { indexedDB.deleteDatabase(dbName); } catch {}
});
it('commit: 事务成功数据持久化', async () => {
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '1', name: 'Alice' });
await trx.table('users').insert({ id: '2', name: 'Bob' });
});
const rows = await db.table('users').select().execute();
expect(rows).toHaveLength(2);
});
it('rollback: 事务失败数据恢复', async () => {
await db.table('users').insert({ id: '1', name: 'Alice' });
try {
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '2', name: 'Bob' });
await trx.table('users').insert({ id: '2', name: 'Dup' });
});
} catch { /* expected */ }
const rows = await db.table('users').select().execute();
expect(rows).toHaveLength(1);
});
});
// ===================================================================
// Hybrid 引擎事务回滚
// ===================================================================
describe('v0.1.13 事务回滚 — Hybrid 模式', () => {
let db: MetonaSqlark;
let dbName: string;
beforeEach(async () => {
dbName = `tx-hybrid-${++idbCounter}`;
db = new MetonaSqlark({ name: dbName, mode: 'hybrid', diskEngine: 'indexeddb' });
await db.init();
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
});
});
afterEach(async () => {
await db.close();
try { indexedDB.deleteDatabase(dbName); } catch {}
});
it('commit: 事务成功', async () => {
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '1', name: 'Alice' });
});
expect(await db.table('users').count()).toBe(1);
});
it('rollback: 事务失败回滚', async () => {
await db.table('users').insert({ id: '1', name: 'Alice' });
try {
await db.transaction(async (trx) => {
await trx.table('users').insert({ id: '2', name: 'Bob' });
await trx.table('users').delete().where({ id: '1' }).execute();
throw new Error('fail');
});
} catch { /* expected */ }
const rows = await db.table('users').select().execute();
expect(rows).toHaveLength(1);
expect(rows[0].id).toBe('1');
});
});