Files
breakpilot-compliance/admin-compliance/e2e/specs/vendor-assessment.spec.ts
T
Benjamin Admin 185d680669
Build + Deploy / build-backend-compliance (push) Successful in 12s
Build + Deploy / build-developer-portal (push) Successful in 10s
Build + Deploy / build-tts (push) Successful in 11s
Build + Deploy / build-document-crawler (push) Successful in 12s
Build + Deploy / build-admin-compliance (push) Successful in 1m51s
Build + Deploy / build-ai-sdk (push) Successful in 15s
Build + Deploy / build-dsms-gateway (push) Successful in 11s
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
Build + Deploy / build-dsms-node (push) Successful in 15s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 15s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m25s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 40s
CI / test-python-backend (push) Successful in 44s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 20s
CI / validate-canonical-controls (push) Successful in 13s
Build + Deploy / trigger-orca (push) Successful in 2m25s
feat(vendor-assessment): E2E tests + remove old use-case-audit
Phase 6-7: Remove /sdk/use-case-audit (questionnaire approach), replace
sidebar with "Vertragspruefung". Add Playwright E2E tests:

- Page load & form validation tests
- Spiegel.de DSE assessment (real URL)
- IHK Berlin multi-document assessment (DSE + Impressum)
- Hetzner AVV auto-detect test
- API direct tests (POST, GET, poll, not-found)
- Cross-check scenario (AVV without TOM → missing TOM finding)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-12 23:37:45 +02:00

316 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Vendor Assessment — Vertragspruefung E2E Tests
*
* Tests the complete flow: Provider → Documents → Analysis → Pruefprotokoll
* Uses real provider URLs (DSE/AGB pages) as test documents.
*
* Test Vendors:
* - Spiegel.de (large publisher, comprehensive DSE)
* - IHK (institutional, formal AGB/DSE)
* - Safetykon (smaller provider, potentially incomplete)
*/
import { test, expect } from '@playwright/test'
const BASE = process.env.PLAYWRIGHT_BASE_URL || 'https://macmini:3007'
// ── Page Load & Navigation ─────────────────────────────────────────
test.describe('Vendor Assessment — Page', () => {
test('page loads and shows upload form', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
const body = await page.textContent('body')
expect(body).toContain('Vertragspruefung')
expect(body).toContain('Auftragsverarbeiter')
expect(body).toContain('Dokumente')
expect(body).toContain('Pruefung starten')
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-page.png',
fullPage: true,
})
})
test('sidebar shows Vertragspruefung link', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
const sidebar = page.locator('nav, [class*="sidebar"], [class*="Sidebar"]')
const sidebarText = await sidebar.textContent()
expect(sidebarText).toContain('Vertragspruefung')
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-sidebar.png',
})
})
test('can add and remove document entries', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
// Initially one entry
const urlInputs = page.locator('input[type="url"]')
await expect(urlInputs).toHaveCount(1)
// Add another
await page.click('button:has-text("Weiteres Dokument")')
await expect(urlInputs).toHaveCount(2)
// Add third
await page.click('button:has-text("Weiteres Dokument")')
await expect(urlInputs).toHaveCount(3)
// Remove second (click × button)
const removeButtons = page.locator('button:has-text("×")')
await removeButtons.nth(1).click()
await expect(urlInputs).toHaveCount(2)
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-multi-doc.png',
fullPage: true,
})
})
test('submit button disabled without vendor name', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
const submitBtn = page.locator('button:has-text("Pruefung starten")')
await expect(submitBtn).toBeDisabled()
// Fill vendor name only
await page.fill('input[placeholder*="SysEleven"]', 'Test GmbH')
// Still disabled — no URL
await expect(submitBtn).toBeDisabled()
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-validation.png',
})
})
})
// ── Real Vendor Assessment Flows ───────────────────────────────────
test.describe('Vendor Assessment — Spiegel.de', () => {
test.setTimeout(120000) // 2 min for full analysis
test('assess Spiegel DSE produces findings', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
// Fill vendor
await page.fill('input[placeholder*="SysEleven"]', 'Spiegel Verlag')
// Fill document URL
await page.fill('input[type="url"]', 'https://www.spiegel.de/datenschutz-spiegel')
// Select doc type
await page.selectOption('select', 'dse')
// Start assessment
await page.click('button:has-text("Pruefung starten")')
// Wait for progress indicator
await expect(page.locator('text=Vertragspruefung laeuft')).toBeVisible({ timeout: 10000 })
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-spiegel-progress.png',
})
// Wait for completion (poll)
await expect(page.locator('text=Spiegel Verlag')).toBeVisible({ timeout: 90000 })
// Pruefprotokoll should show score
const body = await page.textContent('body')
expect(body).toMatch(/\d+%/) // score percentage
// Should have document results
expect(body).toContain('Gepruefte Dokumente')
// Should show DSE checks (since we submitted a DSE)
expect(body).toContain('Dokumente')
expect(body).toContain('Findings')
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-spiegel-result.png',
fullPage: true,
})
})
})
test.describe('Vendor Assessment — IHK', () => {
test.setTimeout(120000)
test('assess IHK with DSE and AGB', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
// Fill vendor
await page.fill('input[placeholder*="SysEleven"]', 'IHK Berlin')
// First doc: DSE
const firstUrl = page.locator('input[type="url"]').first()
await firstUrl.fill('https://www.ihk.de/datenschutzerklaerung')
await page.selectOption('select', 'dse')
// Add second doc: AGB
await page.click('button:has-text("Weiteres Dokument")')
const secondUrl = page.locator('input[type="url"]').nth(1)
await secondUrl.fill('https://www.ihk.de/impressum')
// Second doc type: impressum
const selects = page.locator('select')
await selects.nth(1).selectOption('impressum')
// Start
await page.click('button:has-text("Pruefung starten")')
// Wait for completion
await expect(page.locator('text=IHK Berlin')).toBeVisible({ timeout: 90000 })
const body = await page.textContent('body')
expect(body).toMatch(/\d+%/)
expect(body).toContain('Gepruefte Dokumente')
// Should have 2 documents analyzed
expect(body).toContain('2')
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-ihk-result.png',
fullPage: true,
})
})
})
test.describe('Vendor Assessment — AVV Check', () => {
test.setTimeout(120000)
test('AVV document runs Art. 28 checks', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
// Fill vendor
await page.fill('input[placeholder*="SysEleven"]', 'Hetzner Online GmbH')
// Hetzner has a public AVV
const urlInput = page.locator('input[type="url"]').first()
await urlInput.fill('https://www.hetzner.com/de/legal/privacy-policy/')
// Auto-detect type
await page.selectOption('select', 'auto')
// Start
await page.click('button:has-text("Pruefung starten")')
// Wait for completion
await expect(page.locator('text=Hetzner Online GmbH')).toBeVisible({ timeout: 90000 })
const body = await page.textContent('body')
expect(body).toMatch(/\d+%/)
// Should show at least some category scores
expect(body).toContain('Kategorie')
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-hetzner-result.png',
fullPage: true,
})
})
})
// ── API Direct Tests ───────────────────────────────────────────────
test.describe('Vendor Assessment — API', () => {
test('POST /assessments starts a job', async ({ request }) => {
const resp = await request.post(`${BASE}/api/vendor-compliance/assessments`, {
data: {
vendor_name: 'API Test GmbH',
documents: [
{ doc_type: 'dse', label: 'Test DSE', url: 'https://www.spiegel.de/datenschutz-spiegel' },
],
},
})
expect(resp.ok()).toBeTruthy()
const data = await resp.json()
expect(data.assessment_id).toBeTruthy()
expect(data.status).toBe('running')
})
test('GET /assessments/{id} returns status', async ({ request }) => {
// Start a job first
const startResp = await request.post(`${BASE}/api/vendor-compliance/assessments`, {
data: {
vendor_name: 'Poll Test GmbH',
documents: [
{ doc_type: 'dse', label: 'Test', url: 'https://www.spiegel.de/datenschutz-spiegel' },
],
},
})
const { assessment_id } = await startResp.json()
// Poll immediately — should be running
const statusResp = await request.get(
`${BASE}/api/vendor-compliance/assessments/${assessment_id}`,
)
expect(statusResp.ok()).toBeTruthy()
const status = await statusResp.json()
expect(status.assessment_id).toBe(assessment_id)
expect(['running', 'completed']).toContain(status.status)
})
test('GET /assessments lists all assessments', async ({ request }) => {
const resp = await request.get(`${BASE}/api/vendor-compliance/assessments`)
expect(resp.ok()).toBeTruthy()
const data = await resp.json()
expect(data.assessments).toBeDefined()
expect(Array.isArray(data.assessments)).toBeTruthy()
})
test('GET unknown assessment returns not_found', async ({ request }) => {
const resp = await request.get(
`${BASE}/api/vendor-compliance/assessments/00000000-0000-0000-0000-000000000000`,
)
const data = await resp.json()
expect(data.status).toBe('not_found')
})
})
// ── Cross-Check Scenarios ──────────────────────────────────────────
test.describe('Vendor Assessment — Cross-Check', () => {
test.setTimeout(120000)
test('single AVV without TOM triggers cross-check finding', async ({ page }) => {
await page.goto(`${BASE}/sdk/vendor-assessment`)
await page.waitForLoadState('networkidle')
// Submit only an AVV (no TOM annex) — should trigger cross-check
await page.fill('input[placeholder*="SysEleven"]', 'Cross-Check Test')
const urlInput = page.locator('input[type="url"]').first()
await urlInput.fill('https://www.hetzner.com/de/legal/privacy-policy/')
await page.selectOption('select', 'avv')
await page.click('button:has-text("Pruefung starten")')
// Wait for result
await expect(page.locator('text=Cross-Check Test')).toBeVisible({ timeout: 90000 })
const body = await page.textContent('body')
// Cross-check should detect missing TOM annex
// (The AVV checklist mentions TOM, but no TOM doc was uploaded)
expect(body).toContain('Cross-Check')
await page.screenshot({
path: 'e2e/test-results/vendor-assessment-cross-check.png',
fullPage: true,
})
})
})