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
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>
316 lines
10 KiB
TypeScript
316 lines
10 KiB
TypeScript
/**
|
||
* 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,
|
||
})
|
||
})
|
||
})
|