import { describe, it, expect } from 'vitest'; import { BriefViewDto } from '@shared/infrastructure/api-client'; import { parseBrief, parseBriefView, parseNode, parseOrgTemplate, parseStatus, } from './brief.adapter'; const view: BriefViewDto = { brief: { briefId: 'b1', beroep: 'arts', templateId: 't1', drafterId: 'demo-drafter', status: { tag: 'submitted', submittedBy: 'demo-drafter', submittedAt: '2026-07-01' }, placeholders: [ { key: 'naam', label: 'Naam', autoResolvable: true }, { key: 'code', label: 'Code', autoResolvable: true, fillable: false }, ], sections: [ { sectionKey: 'aanhef', title: 'Aanhef', required: true, blocks: [ { type: 'passage', blockId: 'local-1', content: { paragraphs: [{ nodes: [{ type: 'placeholder', key: 'naam' }] }] }, sourcePassageId: 'p1', sourceVersion: 2, edited: true, }, { type: 'freeText', blockId: 'local-2', content: { paragraphs: [{ nodes: [{ type: 'text', text: 'hoi', marks: ['bold'] }] }] }, }, ], }, ], }, availablePassages: [ { passageId: 'p1', scope: 'global', sectionKey: 'aanhef', label: 'Aanhef', content: { paragraphs: [{ nodes: [] }] }, version: 1, }, { passageId: 'p-neg-scholing', scope: 'global', sectionKey: 'kern', label: 'Onvoldoende scholing', content: { paragraphs: [{ nodes: [{ type: 'text', text: 'x' }] }] }, version: 1, besluit: 'negatief', reason: 'onvoldoende_scholing', }, ], decisions: { canEdit: false, canApprove: true, canReject: true, canSend: false, canRevealBigNummer: false, }, orgTemplate: { subOrgId: 'cibg-registers', orgName: 'CIBG โ€” Registers', returnAddress: 'BIG-register\nPostbus 00000\n2500 AA Den Haag', footerContact: 'www.bigregister.nl\ninfo@voorbeeld.example', footerLegal: 'KvK 00000000', signatureName: 'A. de Vries', signatureRole: 'Hoofd Registratie', signatureClosing: 'Met vriendelijke groet,', margins: { topMm: 25, rightMm: 25, bottomMm: 25, leftMm: 25 }, version: 1, }, caseContext: { zorgverlenerNaam: 'Dr. A. (Anna) de Vries', bigNummer: '19012345601', beroep: 'arts', aanvraagReferentie: 'HER-2026-000842', }, }; describe('brief.adapter parse boundary', () => { it('parses a well-formed view into the domain unions', () => { const r = parseBriefView(view); expect(r.ok).toBe(true); if (!r.ok) return; expect(r.value.brief.status).toEqual({ tag: 'submitted', submittedBy: 'demo-drafter', submittedAt: '2026-07-01', }); const [passage, free] = r.value.brief.sections[0].blocks; expect(passage.type === 'passage' && passage.edited).toBe(true); expect(free.type).toBe('freeText'); expect(r.value.brief.placeholders[1]).toEqual({ key: 'code', label: 'Code', autoResolvable: true, fillable: false, }); expect(r.value.decisions).toEqual({ canEdit: false, canApprove: true, canReject: true, canSend: false, canRevealBigNummer: false, }); // Guided-drafting tags survive the boundary; the untagged passage has neither. expect(r.value.availablePassages[0].besluit).toBeUndefined(); expect(r.value.availablePassages[1]).toMatchObject({ besluit: 'negatief', reason: 'onvoldoende_scholing', }); expect(r.value.caseContext).toEqual({ zorgverlenerNaam: 'Dr. A. (Anna) de Vries', bigNummer: '19012345601', beroep: 'arts', aanvraagReferentie: 'HER-2026-000842', }); }); it('rejects a view whose case context is missing or malformed', () => { expect(parseBriefView({ ...view, caseContext: undefined }).ok).toBe(false); expect( parseBriefView({ ...view, caseContext: { ...view.caseContext!, bigNummer: undefined as never }, }).ok, ).toBe(false); }); it('parses the org template and drops a null logoDocumentId', () => { const r = parseOrgTemplate(view.orgTemplate); expect(r.ok).toBe(true); if (!r.ok) return; expect(r.value.orgName).toBe('CIBG โ€” Registers'); expect(r.value.margins).toEqual({ topMm: 25, rightMm: 25, bottomMm: 25, leftMm: 25 }); expect('logoDocumentId' in r.value).toBe(false); }); it('rejects a view whose org template is missing or malformed', () => { expect(parseBriefView({ ...view, orgTemplate: undefined }).ok).toBe(false); expect(parseOrgTemplate({ ...view.orgTemplate, signatureName: undefined }).ok).toBe(false); expect( parseOrgTemplate({ ...view.orgTemplate, margins: { topMm: 25, rightMm: 25, bottomMm: 25 }, }).ok, ).toBe(false); }); it('rejects a view whose decisions are missing or malformed', () => { expect(parseBriefView({ ...view, decisions: undefined as never }).ok).toBe(false); expect( parseBriefView({ ...view, decisions: { ...view.decisions, canSend: 'yes' as never } }).ok, ).toBe(false); // The PII-reveal flag (PRD-0002 ยง5c) is required at the boundary too. expect( parseBriefView({ ...view, decisions: { ...view.decisions, canRevealBigNummer: undefined as never }, }).ok, ).toBe(false); }); it('narrows node variants and rejects unknown ones', () => { expect(parseNode({ type: 'text', text: 'x' })).toEqual({ ok: true, value: { type: 'text', text: 'x' }, }); expect(parseNode({ type: 'placeholder', key: 'k' })).toEqual({ ok: true, value: { type: 'placeholder', key: 'k' }, }); expect(parseNode({ type: 'lineBreak' })).toEqual({ ok: true, value: { type: 'lineBreak' } }); expect(parseNode({ type: 'text' }).ok).toBe(false); // missing text expect(parseNode({ type: 'bogus' } as never).ok).toBe(false); }); it('rejects a status DTO missing its required fields', () => { expect(parseStatus({ tag: 'submitted' }).ok).toBe(false); // no submittedBy/At expect(parseStatus({ tag: 'rejected', rejectedBy: 'x', rejectedAt: 't' }).ok).toBe(false); // no comments expect(parseStatus({ tag: 'draft' }).ok).toBe(true); }); it('reads section.locked (default false) and paragraph.list', () => { const r = parseBrief({ ...view.brief, sections: [ { sectionKey: 'aanhef', title: 'Aanhef', required: true, locked: true, blocks: [ { type: 'freeText', blockId: 'b1', content: { paragraphs: [ { nodes: [{ type: 'text', text: 'een' }], list: 'bullet' }, { nodes: [{ type: 'text', text: 'twee' }], list: 'number' }, { nodes: [{ type: 'text', text: 'plat' }] }, ], }, }, ], }, { sectionKey: 'kern', title: 'Kern', required: true, blocks: [] }, // no `locked` โ†’ false ], }); expect(r.ok).toBe(true); if (!r.ok) return; const [aanhef, kern] = r.value.sections; expect(aanhef.locked).toBe(true); expect(kern.locked).toBe(false); expect(aanhef.blocks[0].content.paragraphs.map((p) => p.list)).toEqual([ 'bullet', 'number', undefined, ]); }); it('rejects a library passage with an unknown scope', () => { const r = parseBriefView({ ...view, availablePassages: [{ ...view.availablePassages![0], scope: 'bogus' as never }], }); expect(r.ok).toBe(false); }); it('rejects a passage block missing provenance', () => { const r = parseBrief({ ...view.brief, sections: [ { sectionKey: 's', title: 'S', required: false, blocks: [{ type: 'passage', blockId: 'b', content: { paragraphs: [] } }], }, ], }); expect(r.ok).toBe(false); }); });