Files
accounted/tests/pg/agent-atom-registry.pg.test.ts
T
Jakob Wennberg ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.

Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.

Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.

Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:58:06 +02:00

96 lines
4.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { seedCompany } from '@/tests/pg/fixtures'
import { withUserContext } from '@/tests/pg/setup'
describe('agent_atom_registry.pg: seed + RLS', () => {
it('seed migration populated active atoms with non-null bodies and no swarm-* ids', async () => {
const { userId } = await seedCompany()
const rows = await withUserContext(userId, async (client) => {
const res = await client.query<{ id: string; body: string | null; mcp_exposed: boolean }>(
`SELECT id, body, mcp_exposed FROM public.agent_atom_registry WHERE is_active`,
)
return res.rows
})
// The generated seed migration (…_seed_agent_atom_bodies.sql) runs during replay.
expect(rows.length).toBeGreaterThan(0)
// Every active atom has a real body inlined: the production read path depends
// on this (no disk fallback in prod).
for (const r of rows) {
expect(r.body, `atom ${r.id} body should be non-empty`).toBeTruthy()
expect(r.mcp_exposed).toBe(true)
}
// swarm-* audit skills must never become atoms; a curated horizontal must be present.
expect(rows.some((r) => r.id.includes('swarm'))).toBe(false)
expect(rows.some((r) => r.id === 'horizontal/swedish-vat')).toBe(true)
})
it('seeds reference children whose parent_atom_id points at a real top-level skill', async () => {
const { userId } = await seedCompany()
const { children, orphans, vatRef } = await withUserContext(userId, async (client) => {
const childRes = await client.query<{ n: number }>(
`SELECT count(*)::int AS n FROM public.agent_atom_registry WHERE parent_atom_id IS NOT NULL`,
)
// FK already guarantees this, but assert there are no dangling links anyway.
const orphanRes = await client.query<{ n: number }>(
`SELECT count(*)::int AS n
FROM public.agent_atom_registry c
WHERE c.parent_atom_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM public.agent_atom_registry p WHERE p.id = c.parent_atom_id
)`,
)
const vatRefRes = await client.query<{ id: string; body: string | null; parent_atom_id: string }>(
`SELECT id, body, parent_atom_id FROM public.agent_atom_registry
WHERE id = 'horizontal/swedish-vat/vat-compliance-reference'`,
)
return { children: childRes.rows[0]!.n, orphans: orphanRes.rows[0]!.n, vatRef: vatRefRes.rows[0] }
})
expect(children).toBeGreaterThan(0)
expect(orphans).toBe(0)
expect(vatRef?.parent_atom_id).toBe('horizontal/swedish-vat')
expect(vatRef?.body).toBeTruthy()
})
it('catalog filter (parent_atom_id IS NULL) hides references but the parent footer exposes them', async () => {
const { userId } = await seedCompany()
const { listed, vatBody } = await withUserContext(userId, async (client) => {
// The query every catalog uses: top-level skills only.
const listedRes = await client.query<{ id: string }>(
`SELECT id FROM public.agent_atom_registry
WHERE is_active AND parent_atom_id IS NULL`,
)
const vatRes = await client.query<{ body: string }>(
`SELECT body FROM public.agent_atom_registry WHERE id = 'horizontal/swedish-vat'`,
)
return { listed: listedRes.rows.map((r) => r.id), vatBody: vatRes.rows[0]?.body ?? '' }
})
// No reference id (three-segment, "<tier>/<slug>/<ref>") survives the filter.
expect(listed.some((id) => id.split('/').length === 3)).toBe(false)
expect(listed).toContain('horizontal/swedish-vat')
// …yet the loadable id is reachable from the parent body once loaded.
expect(vatBody).toContain('gnubok_load_skill("horizontal/swedish-vat/vat-compliance-reference")')
})
it('authenticated users can read the catalog but not write it', async () => {
const { userId } = await seedCompany()
await withUserContext(userId, async (client) => {
const sel = await client.query<{ n: number }>(
`SELECT count(*)::int AS n FROM public.agent_atom_registry`,
)
expect(sel.rows[0]!.n).toBeGreaterThan(0)
// No INSERT policy for authenticated: the catalog ships via service-role
// migrations only.
await expect(
client.query(
`INSERT INTO public.agent_atom_registry (id, tier, title, description, body_path)
VALUES ('horizontal/hacker', 'horizontal', 'x', 'x', 'x')`,
),
).rejects.toThrow()
})
})
})