/** * 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'); }); });