/** * 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, }) }) })