Files
motovaultpro/backend/src/features/audit-log/__tests__/migrations.test.ts
Eric Gullickson c98211f4a2
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 4m42s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Failing after 6s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 6s
feat: Implement centralized audit logging admin interface (refs #10)
- Add audit_logs table with categories, severities, and indexes
- Create AuditLogService and AuditLogRepository
- Add REST API endpoints for viewing and exporting logs
- Wire audit logging into auth, vehicles, admin, and backup features
- Add desktop AdminLogsPage with filters and CSV export
- Add mobile AdminLogsMobileScreen with card layout
- Implement 90-day retention cleanup job
- Remove old AuditLogPanel from AdminCatalogPage

Security fixes:
- Escape LIKE special characters to prevent pattern injection
- Limit CSV export to 5000 records to prevent memory exhaustion
- Add truncation warning headers for large exports

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 11:09:09 -06:00

131 lines
4.0 KiB
TypeScript

/**
* @ai-summary Integration tests for audit_logs table migration
* @ai-context Tests table creation, constraints, and indexes
*/
import { Pool } from 'pg';
import { appConfig } from '../../../core/config/config-loader';
describe('Audit Logs Migration', () => {
let pool: Pool;
beforeAll(async () => {
pool = new Pool({
connectionString: appConfig.getDatabaseUrl(),
});
});
afterAll(async () => {
await pool.end();
});
describe('Table Structure', () => {
it('should have audit_logs table with correct columns', async () => {
const result = await pool.query(`
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'audit_logs'
ORDER BY ordinal_position
`);
const columns = result.rows.map((row) => row.column_name);
expect(columns).toContain('id');
expect(columns).toContain('category');
expect(columns).toContain('severity');
expect(columns).toContain('user_id');
expect(columns).toContain('action');
expect(columns).toContain('resource_type');
expect(columns).toContain('resource_id');
expect(columns).toContain('details');
expect(columns).toContain('created_at');
});
});
describe('CHECK Constraints', () => {
it('should accept valid category values', async () => {
const validCategories = ['auth', 'vehicle', 'user', 'system', 'admin'];
for (const category of validCategories) {
const result = await pool.query(
`INSERT INTO audit_logs (category, severity, action)
VALUES ($1, 'info', 'test action')
RETURNING id`,
[category]
);
expect(result.rows[0].id).toBeDefined();
// Cleanup
await pool.query('DELETE FROM audit_logs WHERE id = $1', [result.rows[0].id]);
}
});
it('should reject invalid category values', async () => {
await expect(
pool.query(
`INSERT INTO audit_logs (category, severity, action)
VALUES ('invalid', 'info', 'test action')`
)
).rejects.toThrow();
});
it('should accept valid severity values', async () => {
const validSeverities = ['info', 'warning', 'error'];
for (const severity of validSeverities) {
const result = await pool.query(
`INSERT INTO audit_logs (category, severity, action)
VALUES ('auth', $1, 'test action')
RETURNING id`,
[severity]
);
expect(result.rows[0].id).toBeDefined();
// Cleanup
await pool.query('DELETE FROM audit_logs WHERE id = $1', [result.rows[0].id]);
}
});
it('should reject invalid severity values', async () => {
await expect(
pool.query(
`INSERT INTO audit_logs (category, severity, action)
VALUES ('auth', 'invalid', 'test action')`
)
).rejects.toThrow();
});
});
describe('Nullable Columns', () => {
it('should allow NULL user_id for system actions', async () => {
const result = await pool.query(
`INSERT INTO audit_logs (category, severity, user_id, action)
VALUES ('system', 'info', NULL, 'system startup')
RETURNING id, user_id`
);
expect(result.rows[0].id).toBeDefined();
expect(result.rows[0].user_id).toBeNull();
// Cleanup
await pool.query('DELETE FROM audit_logs WHERE id = $1', [result.rows[0].id]);
});
});
describe('Indexes', () => {
it('should have required indexes', async () => {
const result = await pool.query(`
SELECT indexname
FROM pg_indexes
WHERE tablename = 'audit_logs'
`);
const indexNames = result.rows.map((row) => row.indexname);
expect(indexNames).toContain('idx_audit_logs_category_created');
expect(indexNames).toContain('idx_audit_logs_severity_created');
expect(indexNames).toContain('idx_audit_logs_user_created');
expect(indexNames).toContain('idx_audit_logs_created');
expect(indexNames).toContain('idx_audit_logs_action_gin');
});
});
});