148 lines
4.2 KiB
TypeScript
148 lines
4.2 KiB
TypeScript
import { strict as assert } from 'node:assert';
|
|
import { describe, it } from 'node:test';
|
|
import type { Database } from '@pig/db';
|
|
import { facts } from '@pig/db';
|
|
import type { Principal } from '../src/lib/auth';
|
|
import { executeMutation, MutationError } from '../src/lib/mutation';
|
|
import { factDecisionDefinition } from '../src/routes/facts';
|
|
|
|
const reviewer: Principal = {
|
|
userId: '00000000-0000-0000-0000-000000000001',
|
|
email: 'research@example.com',
|
|
name: 'Research reviewer',
|
|
isPlatformAdmin: false,
|
|
teams: [{ team: 'research', role: 'admin' }],
|
|
via: 'jwt',
|
|
scopes: ['read', 'write'],
|
|
};
|
|
|
|
const proposedFact = {
|
|
id: '10000000-0000-0000-0000-000000000001',
|
|
accountId: '20000000-0000-0000-0000-000000000001',
|
|
contactId: null,
|
|
field: 'supplierType',
|
|
value: 'neocloud',
|
|
score: '0.780',
|
|
band: 'probable',
|
|
status: 'proposed',
|
|
evidence: { excerpt: 'Operates dedicated GPU cloud regions.' },
|
|
sourceUrl: 'https://example.com/infrastructure',
|
|
method: 'web_search',
|
|
agentRunId: null,
|
|
decidedByUserId: null,
|
|
decidedAt: null,
|
|
observedAt: new Date('2026-08-12T10:00:00Z'),
|
|
supersededAt: null,
|
|
createdAt: new Date('2026-08-12T10:00:00Z'),
|
|
} as const;
|
|
|
|
function fakeDatabase(initial: Record<string, unknown>) {
|
|
let stored = { ...initial };
|
|
const updates: { table: unknown; values: Record<string, unknown> }[] = [];
|
|
const activities: Record<string, unknown>[] = [];
|
|
|
|
const tx = {
|
|
select: () => ({
|
|
from: () => ({
|
|
where: () => ({ limit: async () => [stored] }),
|
|
}),
|
|
}),
|
|
update: (table: unknown) => ({
|
|
set: (values: Record<string, unknown>) => ({
|
|
where: () => ({
|
|
returning: async () => {
|
|
updates.push({ table, values });
|
|
stored = { ...stored, ...values };
|
|
return [stored];
|
|
},
|
|
}),
|
|
}),
|
|
}),
|
|
insert: () => ({
|
|
values: async (row: Record<string, unknown>) => {
|
|
activities.push(row);
|
|
},
|
|
}),
|
|
};
|
|
|
|
const db = {
|
|
transaction: async (work: (transaction: unknown) => Promise<unknown>) => work(tx),
|
|
} as unknown as Database;
|
|
|
|
return { db, updates, activities };
|
|
}
|
|
|
|
describe('fact review decisions', () => {
|
|
it('approves evidence without applying an arbitrary field to the CRM record', async () => {
|
|
const state = fakeDatabase(proposedFact);
|
|
|
|
const result = await executeMutation(
|
|
state.db,
|
|
reviewer,
|
|
async () => ({ status: 'approved' }),
|
|
factDecisionDefinition,
|
|
{ id: proposedFact.id },
|
|
);
|
|
|
|
assert.equal(result.fact.status, 'approved');
|
|
assert.equal(result.recordUpdated, false);
|
|
assert.equal(state.updates.length, 1);
|
|
assert.equal(state.updates[0]?.table, facts);
|
|
assert.deepEqual(state.updates[0]?.values, {
|
|
status: 'approved',
|
|
decidedByUserId: reviewer.userId,
|
|
decidedAt: state.updates[0]?.values.decidedAt,
|
|
});
|
|
assert.ok(state.updates[0]?.values.decidedAt instanceof Date);
|
|
assert.deepEqual(state.activities[0]?.meta, {
|
|
factId: proposedFact.id,
|
|
decision: 'approved',
|
|
field: proposedFact.field,
|
|
recordUpdated: false,
|
|
});
|
|
});
|
|
|
|
it('refuses to approve an unsupported claim', async () => {
|
|
const state = fakeDatabase({
|
|
...proposedFact,
|
|
evidence: null,
|
|
sourceUrl: null,
|
|
});
|
|
|
|
await assert.rejects(
|
|
executeMutation(
|
|
state.db,
|
|
reviewer,
|
|
async () => ({ status: 'approved' }),
|
|
factDecisionDefinition,
|
|
{ id: proposedFact.id },
|
|
),
|
|
(error: unknown) =>
|
|
error instanceof MutationError && error.code === 'missing_evidence',
|
|
);
|
|
assert.deepEqual(state.updates, []);
|
|
assert.deepEqual(state.activities, []);
|
|
});
|
|
|
|
it('allows an unsupported proposal to be dismissed without manufacturing evidence', async () => {
|
|
const state = fakeDatabase({
|
|
...proposedFact,
|
|
evidence: null,
|
|
sourceUrl: null,
|
|
});
|
|
|
|
const result = await executeMutation(
|
|
state.db,
|
|
reviewer,
|
|
async () => ({ status: 'dismissed' }),
|
|
factDecisionDefinition,
|
|
{ id: proposedFact.id },
|
|
);
|
|
|
|
assert.equal(result.fact.status, 'dismissed');
|
|
assert.equal(result.recordUpdated, false);
|
|
assert.equal(state.updates.length, 1);
|
|
assert.equal(state.updates[0]?.table, facts);
|
|
});
|
|
});
|