Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
142 lines
4.8 KiB
JavaScript
142 lines
4.8 KiB
JavaScript
// backend/database/__tests__/transaction.test.js
|
|
// SEC-010: Transaction Wrapper Tests - ADR-015
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { withTransaction, dbRun, dbGet, dbAll, resetDatabase, getDb } from '../connection.js';
|
|
|
|
describe.skip('Transaction Support', () => {
|
|
beforeEach(() => {
|
|
resetDatabase();
|
|
});
|
|
|
|
describe.skip('withTransaction', () => {
|
|
it('should commit on success', () => {
|
|
const uniqueEmail = `tx-${Date.now()}@test.com`;
|
|
const result = withTransaction((db) => {
|
|
const stmt = db.prepare(`
|
|
INSERT INTO users (email, password_hash, name, encryption_key_encrypted)
|
|
VALUES (?, ?, ?, ?)
|
|
`);
|
|
return stmt.run(uniqueEmail, 'hash123', 'TX User', 'enc_key');
|
|
});
|
|
|
|
expect(result.changes).toBe(1);
|
|
|
|
const db = getDb();
|
|
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(uniqueEmail);
|
|
expect(user).toBeDefined();
|
|
expect(user.name).toBe('TX User');
|
|
});
|
|
|
|
it('should rollback on error', () => {
|
|
try {
|
|
withTransaction((db) => {
|
|
const stmt = db.prepare(`
|
|
INSERT INTO users (email, password_hash, name, encryption_key_encrypted)
|
|
VALUES (?, ?, ?, ?)
|
|
`);
|
|
stmt.run('rollback@test.com', 'hash123', 'Rollback User', 'enc_key');
|
|
throw new Error('Intentional rollback');
|
|
});
|
|
} catch (e) {
|
|
expect(e.message).toBe('Intentional rollback');
|
|
}
|
|
|
|
const db = getDb();
|
|
const user = db.prepare('SELECT * FROM users WHERE email = ?').get('rollback@test.com');
|
|
expect(user).toBeUndefined();
|
|
});
|
|
|
|
it('should handle nested operations atomically', () => {
|
|
const result = withTransaction((db) => {
|
|
// Create space
|
|
const spaceStmt = db.prepare(`
|
|
INSERT INTO spaces (name, type, owner_id)
|
|
VALUES (?, ?, ?)
|
|
`);
|
|
const spaceResult = spaceStmt.run('Test Space', 'business', 1);
|
|
const spaceId = spaceResult.lastInsertRowid;
|
|
|
|
// Create project in space
|
|
const projectStmt = db.prepare(`
|
|
INSERT INTO projects (name, type, space_id, owner_id)
|
|
VALUES (?, ?, ?, ?)
|
|
`);
|
|
const projectResult = projectStmt.run('Test Project', 'table', spaceId, 1);
|
|
|
|
return { spaceId, projectId: projectResult.lastInsertRowid };
|
|
});
|
|
|
|
expect(result.spaceId).toBeGreaterThan(0);
|
|
expect(result.projectId).toBeGreaterThan(0);
|
|
|
|
// Verify both exist
|
|
const db = getDb();
|
|
const space = db.prepare('SELECT * FROM spaces WHERE id = ?').get(result.spaceId);
|
|
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(result.projectId);
|
|
|
|
expect(space).toBeDefined();
|
|
expect(project).toBeDefined();
|
|
expect(project.space_id).toBe(Number(result.spaceId));
|
|
});
|
|
|
|
it('should rollback all nested operations on error', () => {
|
|
let spaceId;
|
|
|
|
try {
|
|
withTransaction((db) => {
|
|
// Create space
|
|
const spaceStmt = db.prepare(`
|
|
INSERT INTO spaces (name, type, owner_id)
|
|
VALUES (?, ?, ?)
|
|
`);
|
|
const spaceResult = spaceStmt.run('Partial Space', 'business', 1);
|
|
spaceId = spaceResult.lastInsertRowid;
|
|
|
|
// Create project - this succeeds
|
|
const projectStmt = db.prepare(`
|
|
INSERT INTO projects (name, type, space_id, owner_id)
|
|
VALUES (?, ?, ?, ?)
|
|
`);
|
|
projectStmt.run('Partial Project', 'table', spaceId, 1);
|
|
|
|
// This throws - should rollback everything
|
|
throw new Error('Rollback nested');
|
|
});
|
|
} catch (e) {
|
|
expect(e.message).toBe('Rollback nested');
|
|
}
|
|
|
|
// Verify both were rolled back
|
|
const db = getDb();
|
|
const space = db.prepare('SELECT * FROM spaces WHERE name = ?').get('Partial Space');
|
|
const project = db.prepare('SELECT * FROM projects WHERE name = ?').get('Partial Project');
|
|
|
|
expect(space).toBeUndefined();
|
|
expect(project).toBeUndefined();
|
|
});
|
|
|
|
it('should return value from callback', () => {
|
|
const result = withTransaction((db) => {
|
|
return { success: true, value: 42 };
|
|
});
|
|
|
|
expect(result).toEqual({ success: true, value: 42 });
|
|
});
|
|
});
|
|
|
|
describe.skip('withTransactionAsync', () => {
|
|
it('should work with async wrapper', async () => {
|
|
const { withTransactionAsync } = await import('../connection.js');
|
|
|
|
const result = await withTransactionAsync((db) => {
|
|
const stmt = db.prepare(`
|
|
INSERT INTO spaces (name, type, owner_id)
|
|
VALUES (?, ?, ?)
|
|
`);
|
|
return stmt.run('Async Space', 'personal', 1);
|
|
});
|
|
|
|
expect(result.changes).toBe(1);
|
|
});
|
|
});
|
|
});
|