Files
pig/apps/api/test/mutation.test.ts
T
2026-08-13 01:39:01 -07:00

123 lines
4.0 KiB
TypeScript

import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import { z } from 'zod';
import type { Database } from '@pig/db';
import type { Principal } from '../src/lib/auth';
import { AuthError } from '../src/lib/auth';
import { apiError, executeMutation, MutationError } from '../src/lib/mutation';
const principal: Principal = {
userId: '00000000-0000-0000-0000-000000000001',
email: 'seller@example.com',
name: 'Seller',
isPlatformAdmin: false,
teams: [{ team: 'demand', role: 'member' }],
via: 'jwt',
scopes: ['read', 'write'],
};
function fakeDatabase(events: string[], activityRows: unknown[]): Database {
const tx = {
insert: () => ({
values: async (row: unknown) => {
events.push('activity');
activityRows.push(row);
},
}),
};
return {
transaction: async (work: (transaction: unknown) => Promise<unknown>) => {
events.push('transaction');
return work(tx);
},
} as unknown as Database;
}
describe('mutation convention', () => {
it('checks capability before reading attacker-controlled input', async () => {
const events: string[] = [];
const forbidden = { ...principal, teams: [{ team: 'supply', role: 'admin' }] } as Principal;
await assert.rejects(
executeMutation(fakeDatabase(events, []), forbidden, async () => {
events.push('body');
return {};
}, {
schema: z.object({ name: z.string() }),
permission: { capability: 'deal:write', team: 'demand' },
invalidMessage: 'Invalid deal.',
async mutate() {
events.push('mutate');
return { data: {}, activity: { type: 'note', subject: 'Changed' } };
},
}),
(error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
);
assert.deepEqual(events, []);
});
it('rejects invalid ontology input before opening a transaction', async () => {
const events: string[] = [];
const stages = ['qualification', 'legal'] as const;
await assert.rejects(
executeMutation(fakeDatabase(events, []), principal, async () => ({ stage: 'invented' }), {
schema: z.object({ stage: z.enum(stages) }),
permission: { capability: 'deal:write', team: 'demand' },
invalidMessage: 'Invalid transition.',
async mutate() {
events.push('mutate');
return { data: {}, activity: { type: 'stage_change', subject: 'Changed' } };
},
}),
(error: unknown) =>
error instanceof MutationError &&
error.code === 'invalid_request' &&
apiError(error.code, error.message, error.issues).issues?.length === 1,
);
assert.deepEqual(events, []);
});
it('writes mutation evidence in the same transaction with framework attribution', async () => {
const events: string[] = [];
const rows: unknown[] = [];
const result = await executeMutation(
fakeDatabase(events, rows),
principal,
async () => ({ stage: 'legal' }),
{
schema: z.object({ stage: z.literal('legal') }),
permission: { capability: 'deal:write', team: 'demand' },
invalidMessage: 'Invalid transition.',
async mutate() {
events.push('mutate');
const data = { id: 'deal-1' };
return {
data,
activity: {
type: 'stage_change',
subject: 'Moved to legal',
demandDealId: data.id,
},
};
},
},
);
assert.deepEqual(result, { id: 'deal-1' });
assert.deepEqual(events, ['transaction', 'mutate', 'activity']);
assert.deepEqual(rows, [
{
type: 'stage_change',
subject: 'Moved to legal',
demandDealId: 'deal-1',
actorUserId: principal.userId,
actorAgent: null,
source: 'manual',
occurredAt: (rows[0] as { occurredAt: Date }).occurredAt,
},
]);
assert.ok((rows[0] as { occurredAt: unknown }).occurredAt instanceof Date);
});
});