diff --git a/admin-compliance/e2e/specs/compliance-advisor.spec.ts b/admin-compliance/e2e/specs/compliance-advisor.spec.ts new file mode 100644 index 00000000..e176794a --- /dev/null +++ b/admin-compliance/e2e/specs/compliance-advisor.spec.ts @@ -0,0 +1,99 @@ +/** + * E2E: Compliance Advisor — Clarity Gate (v3 contract) + * + * Drives the floating advisor widget end-to-end against a stubbed /api/sdk/compliance-advisor/chat + * (contract fixtures), so the whole FE chain is exercised without the RAG/LLM backend: + * - underspecified question -> clarify mode (L1 general answer + domain context chips) + * - specific question -> answer mode (markdown + [n] citation coupling + evidence pane) + * - clarify -> pick a context -> scoped answer + * Runs on CI / macmini (needs the Next app on :3002). + */ + +import { test, expect } from '../fixtures/sdk-fixtures' + +const CHAT_ROUTE = '**/api/sdk/compliance-advisor/chat' +const openAdvisor = 'Compliance Advisor oeffnen' +const inputPlaceholder = 'Frage eingeben...' + +const CLARIFY = { + mode: 'clarify', + question: 'Was ist PDCA?', + clarity: { + is_underspecified: true, + concentration: 0.3, + suggested_contexts: [ + { id: 'datenschutz', label: 'Datenschutz' }, + { id: 'cyber', label: 'Cybersecurity' }, + ], + }, + general_answer: 'PDCA steht für **Plan-Do-Check-Act**.', + answer: null, + evidence: [], + citations: [], + visual_evidence: [], + footnotes: [], +} + +const ANSWER = { + mode: 'answer', + question: 'CRA Meldefrist', + clarity: { is_underspecified: false, dominant_context: 'cyber', concentration: 0.88 }, + answer: 'Die Meldung erfolgt unverzüglich [1].', + evidence: [ + { evidence_id: 'e1', document: 'CRA', section: 'Art. 14', paragraph: 'Abs. 1', snippet: 'unverzüglich melden' }, + ], + citations: [ + { citation_id: 'c1', number: 1, evidence_id: 'e1', document: 'CRA', section: 'Art. 14', paragraph: 'Abs. 1' }, + ], + visual_evidence: [], + footnotes: [], +} + +async function ask(page: import('@playwright/test').Page, question: string) { + await page.getByRole('button', { name: openAdvisor }).click() + const input = page.getByPlaceholder(inputPlaceholder) + await input.fill(question) + await input.press('Enter') +} + +test.describe('Compliance Advisor — Clarity Gate', () => { + test('underspecified question -> clarify (L1 definition + context chips, no evidence)', async ({ sdkPage }) => { + await sdkPage.route(CHAT_ROUTE, (r) => + r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(CLARIFY) }), + ) + await ask(sdkPage, 'Was ist PDCA?') + + await expect(sdkPage.getByText('Allgemeine Definition')).toBeVisible() + await expect(sdkPage.getByText('Plan-Do-Check-Act')).toBeVisible() + await expect(sdkPage.getByRole('button', { name: 'Datenschutz' })).toBeVisible() + await expect(sdkPage.getByRole('button', { name: 'Cybersecurity' })).toBeVisible() + }) + + test('specific question -> answer with [n] citation + evidence pane', async ({ sdkPage }) => { + await sdkPage.route(CHAT_ROUTE, (r) => + r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(ANSWER) }), + ) + await ask(sdkPage, 'CRA Meldefrist') + + await expect(sdkPage.getByText(/unverzüglich/)).toBeVisible() + await expect(sdkPage.getByTitle('Beleg 1 anzeigen')).toBeVisible() + await expect(sdkPage.getByText('Cyber Resilience Act (CRA)')).toBeVisible() + }) + + test('clarify -> pick a context -> scoped answer', async ({ sdkPage }) => { + let calls = 0 + await sdkPage.route(CHAT_ROUTE, (r) => { + calls += 1 + r.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(calls === 1 ? CLARIFY : ANSWER), + }) + }) + await ask(sdkPage, 'Was ist PDCA?') + await sdkPage.getByRole('button', { name: 'Datenschutz' }).click() + + await expect(sdkPage.getByText(/unverzüglich/)).toBeVisible() + await expect(sdkPage.getByTitle('Beleg 1 anzeigen')).toBeVisible() + }) +})