feat: real audit_log with redaction + pending_changes; replace stub
This commit is contained in:
46
tests/repos/audit.test.js
Normal file
46
tests/repos/audit.test.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { resetDb } from '../helpers/db.js';
|
||||
import { migrateUp } from '../../lib/db/migrate.js';
|
||||
import * as spaces from '../../lib/db/repos/spaces.js';
|
||||
import * as audit from '../../lib/db/repos/audit.js';
|
||||
|
||||
const owner = { kind: 'user', id: null };
|
||||
beforeEach(async () => { await resetDb(); await migrateUp(); });
|
||||
|
||||
describe('audit log', () => {
|
||||
it('creates an audit row on space create', async () => {
|
||||
const s = await spaces.create({ slug: 'h', name: 'H' }, owner);
|
||||
const rows = await audit.listForEntity('space', s.id);
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0].action).toBe('create');
|
||||
});
|
||||
|
||||
it('records the diff of an update', async () => {
|
||||
const s = await spaces.create({ slug: 'h', name: 'H' }, owner);
|
||||
await spaces.update(s.id, { name: 'Hh' }, owner);
|
||||
const rows = await audit.listForEntity('space', s.id);
|
||||
const upd = rows.find(r => r.action === 'update');
|
||||
expect(upd.diff.changes.name.before).toBe('H');
|
||||
expect(upd.diff.changes.name.after).toBe('Hh');
|
||||
});
|
||||
|
||||
it('redacts known sensitive keys', async () => {
|
||||
await audit.recordAudit(owner, 'update', 'test', null,
|
||||
{ token: 'secret-abc' }, { token: 'secret-def' }
|
||||
);
|
||||
const rows = await audit.listByActor({});
|
||||
const r = rows.find(r => r.entity_type === 'test');
|
||||
expect(r.diff.changes.token.before).toBe('[REDACTED]');
|
||||
expect(r.diff.changes.token.after).toBe('[REDACTED]');
|
||||
});
|
||||
|
||||
it('redacts sensitive keys in nested objects', async () => {
|
||||
await audit.recordAudit(owner, 'create', 'nested', null,
|
||||
null, { outer: { password: 'hunter2', name: 'ok' } }
|
||||
);
|
||||
const rows = await audit.listByActor({});
|
||||
const r = rows.find(r => r.entity_type === 'nested');
|
||||
expect(r.diff.after.outer.password).toBe('[REDACTED]');
|
||||
expect(r.diff.after.outer.name).toBe('ok');
|
||||
});
|
||||
});
|
||||
34
tests/repos/pending_changes.test.js
Normal file
34
tests/repos/pending_changes.test.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { resetDb } from '../helpers/db.js';
|
||||
import { migrateUp } from '../../lib/db/migrate.js';
|
||||
import * as agents from '../../lib/db/repos/agents.js';
|
||||
import * as pending from '../../lib/db/repos/pending_changes.js';
|
||||
|
||||
const owner = { kind: 'user', id: null };
|
||||
beforeEach(async () => { await resetDb(); await migrateUp(); });
|
||||
|
||||
describe('pending changes', () => {
|
||||
it('creates and resolves a pending change', async () => {
|
||||
const a = await agents.create({ slug: 'mercy', name: 'Mercy', kind: 'claude' }, owner);
|
||||
const p = await pending.create({
|
||||
agent_id: a.id, entity_type: 'page', action: 'create',
|
||||
payload: { title: 'draft', body_md: 'hi' }, reason: 'inferred from chat'
|
||||
});
|
||||
expect(p.status).toBe('pending');
|
||||
const resolved = await pending.resolve(p.id, 'approved', 'owner');
|
||||
expect(resolved.status).toBe('approved');
|
||||
});
|
||||
|
||||
it('listPending returns only pending rows', async () => {
|
||||
const a = await agents.create({ slug: 'mercy', name: 'Mercy', kind: 'claude' }, owner);
|
||||
const p1 = await pending.create({
|
||||
agent_id: a.id, entity_type: 'page', action: 'create', payload: { title: 'a' }
|
||||
});
|
||||
await pending.create({
|
||||
agent_id: a.id, entity_type: 'page', action: 'create', payload: { title: 'b' }
|
||||
});
|
||||
await pending.resolve(p1.id, 'rejected', 'owner');
|
||||
const pendingRows = await pending.listPending();
|
||||
expect(pendingRows).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user