/** * The write that used to bypass everything. * * `POST /api/activities` lived inline in app.ts with no capability check at * all: any member, and any write-scoped API key, could insert an activity * against an arbitrary `accountId` and move that account's `lastActivityAt`. * These pin the three things that stopped it, not the SQL that carries them * out. */ import { strict as assert } from 'node:assert'; import { describe, it } from 'node:test'; import type { Database } from '@pig/db'; import { AuthError } from '../src/lib/auth'; import { executeMutation } from '../src/lib/mutation'; import { createActivityMutationDefinition, type LoggedActivity } from '../src/routes/activities'; import { onTeam, principal } from './helpers/principal'; interface Recorded { events: string[]; inserted: unknown[]; updated: unknown[]; } /** A transaction whose account lookup answers with a chosen side. */ function database(accountSide: string | null): { db: Database; log: Recorded } { const log: Recorded = { events: [], inserted: [], updated: [] }; const accountRows = accountSide ? [{ side: accountSide }] : []; const tx = { select: () => { log.events.push('select'); return { from: () => ({ where: () => ({ limit: async () => accountRows }) }) }; }, insert: () => ({ values: (row: unknown) => { log.events.push('insert'); log.inserted.push(row); return { onConflictDoNothing: () => ({ returning: async () => [row] }) }; }, }), update: () => ({ set: (values: unknown) => ({ where: async () => { log.events.push('touch-account'); log.updated.push(values); }, }), }), }; return { db: { transaction: async (work: (t: unknown) => Promise) => { log.events.push('transaction'); return work(tx); }, } as unknown as Database, log, }; } const body = { type: 'call' as const, subject: 'Spoke to the CTO', accountId: '00000000-0000-4000-8000-0000000000ff', }; function log(db: Database, actor = principal()) { return executeMutation( db, actor, async () => body, createActivityMutationDefinition(), ) as Promise; } describe('logging an activity', () => { it('refuses a principal with no activity:write anywhere, before reading the body', async () => { const { db, log: recorded } = database('demand'); let bodyWasRead = false; await assert.rejects( executeMutation( db, principal(onTeam('demand', 'viewer')), async () => { bodyWasRead = true; return body; }, createActivityMutationDefinition(), ), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission', ); assert.equal(bodyWasRead, false); assert.deepEqual(recorded.events, []); }); /** * The escalation the old handler allowed: a research member logging a call * against a demand account they have no relationship with, and pushing it to * the top of somebody else's account list. */ it('refuses a research member writing against a demand account', async () => { const { db, log: recorded } = database('demand'); await assert.rejects( log(db, principal(onTeam('research', 'admin'))), (error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission', ); assert.equal(recorded.inserted.length, 0); assert.equal(recorded.updated.length, 0); }); it('admits a demand member against a dual-sided account', async () => { const { db, log: recorded } = database('both'); const result = await log(db); assert.equal(result.deduplicated, false); assert.deepEqual(recorded.events, ['transaction', 'select', 'insert', 'touch-account']); }); it('writes one row, not two — the activity is its own audit event', async () => { const { db, log: recorded } = database('demand'); await log(db); assert.equal( recorded.inserted.length, 1, 'an audit row alongside the activity would double every synced call in the feed', ); }); it('attributes an API key to the agent, not silently to the person', async () => { const { db, log: recorded } = database('demand'); await log(db, principal({ via: 'api_key', apiKeyId: 'key-1' })); assert.deepEqual( recorded.inserted[0] as Record, { ...(recorded.inserted[0] as Record), actorAgent: 'agent', source: 'agent', }, ); }); it('keeps the caller\'s timestamp, because sync backfills', async () => { const { db, log: recorded } = database('demand'); const when = '2026-01-05T09:30:00.000Z'; await executeMutation( db, principal(), async () => ({ ...body, occurredAt: when }), createActivityMutationDefinition(), ); const row = recorded.inserted[0] as { occurredAt: Date }; assert.equal(row.occurredAt.toISOString(), when); // And the account stamp follows the event, not the clock, or a backfilled // call from March would jump the account to the top of the list today. assert.deepEqual(recorded.updated, [{ lastActivityAt: new Date(when) }]); }); });