teqo-backup/tests/unit/readOnlyExtract.unit.spec.ts
Francisco Solla cb5854d17e feat(C187): dossiê Solla por instituição + boletim modelo (skill + builder) (Closes #1135)
Generaliza aditivamente o pipeline C186 (default município; specs verdes) via o
descritor scripts/lib/dossieUnit.mjs (municipality|institution), extrai o emit
A4/fit-guard para scripts/lib/buildPdf.mjs e a receita read-only para
scripts/lib/readOnlyExtract.mjs.

- Catálogo fail-closed src/lib/institutionCatalog.ts + institutionNameAliases.ts
  (seed UFBA/Correios/Enfermagem; escape one-off --slug/--name; dedup).
- Skill/command/subagente /dossie-solla-instituicao; eras A/B/C reusadas.
- Abrangência instituição × setor × rede nunca somada; era sem fonte vira página
  de lacuna explícita; lista capada declara "e mais N" (regra C188 no dono).
- Boletim modelo 1×A4 com fase junto ao valor, sem fontes, sem CTA; herda apenas
  fatos com fonte do dossiê (incl. falas do acervo read-only).
- Sem schema/migration/DB write; artefatos gitignored.
2026-09-17 19:25:13 -03:00

64 lines
2.3 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { enterReadOnlySession, resolveCampaignActor } from '../../scripts/lib/readOnlyExtract.mjs'
// C187 extracted the read-only extraction contract so the institution extractor
// does not twin it. Pins the fail-closed opt-in, the forced read-only URL and
// the actor resolution order.
const originalDatabaseUrl = process.env.DATABASE_URL
afterEach(() => {
if (originalDatabaseUrl === undefined) delete process.env.DATABASE_URL
else process.env.DATABASE_URL = originalDatabaseUrl
delete process.env.READONLY_TEST_CONFIRM
})
describe('enterReadOnlySession', () => {
it('refuses to run without the explicit confirmation env', () => {
const die = vi.fn((message: string) => {
throw new Error(message)
})
expect(() =>
enterReadOnlySession({ confirmEnv: 'READONLY_TEST_CONFIRM', label: 'test', die }),
).toThrow(/READONLY_TEST_CONFIRM=1/)
expect(die).toHaveBeenCalled()
})
it('forces default_transaction_read_only on and returns the raw url', () => {
process.env.READONLY_TEST_CONFIRM = '1'
process.env.DATABASE_URL = 'postgresql://user:pass@127.0.0.1:5432/teqo_test'
const die = vi.fn()
const raw = enterReadOnlySession({
confirmEnv: 'READONLY_TEST_CONFIRM',
label: 'test',
die,
context: 'instituição ufba',
})
expect(raw).toBe('postgresql://user:pass@127.0.0.1:5432/teqo_test')
expect(decodeURIComponent(process.env.DATABASE_URL ?? '')).toContain(
'default_transaction_read_only=on',
)
expect(die).not.toHaveBeenCalled()
})
})
describe('resolveCampaignActor', () => {
it('prefers candidate, then coordinator', async () => {
const find = vi
.fn()
.mockResolvedValueOnce({ docs: [] })
.mockResolvedValueOnce({ docs: [{ id: 7, role: 'coordinator', name: 'Coord' }] })
const actor = await resolveCampaignActor({ find }, { die: vi.fn() })
expect(actor).toMatchObject({ id: 7, role: 'coordinator', collection: 'campaignUser' })
expect(find).toHaveBeenCalledTimes(2)
})
it('fails closed when no privileged actor exists', async () => {
const find = vi.fn().mockResolvedValue({ docs: [] })
const die = vi.fn((message: string) => {
throw new Error(message)
})
await expect(resolveCampaignActor({ find }, { die })).rejects.toThrow(/candidate\/coordinator/)
})
})