Files
MetonaSqlark/tests/transaction-rollback.test.ts
T
thzxx 24b3ca1079
CI / test (20.x) (push) Successful in 10m53s
CI / test (22.x) (push) Successful in 10m47s
CI / test (24.x) (push) Successful in 10m42s
CI / e2e (push) Successful in 10m27s
CI / test (18.x) (push) Successful in 11m1s
release: v0.6.0 — 完全移除 IndexedDB,自研 KVStore 事务存储引擎(多key原子写/快照日志恢复/CRC自愈)+ KVStoreEngine + 旧库迁移工具 + 10万级压力验证 + 崩溃注入e2e
2026-08-10 13:56:04 +08:00

211 lines
6.8 KiB
TypeScript

/**
* v0.1.13 事务回滚测试
* 覆盖 Memory / IndexedDB / Hybrid 三种引擎的 begin/commit/rollback
*/
import { MetonaSqlark } from '../src/core';
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: 'opfs' });
await db.init();
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
});
});
afterEach(async () => {
await db.close();
});
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: 'opfs' });
await db.init();
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
});
});
afterEach(async () => {
await db.close();
});
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');
});
});