import { test, expect } from '@playwright/test' /** * CMP Phase 3 + DSR Integration Tests * * Tests the complete CMP lifecycle including: * - Vendor-agnostic consent fields (consent_method, browser, os, etc.) * - Script/cookie tracking (scripts_blocked, scripts_released, cookies_set) * - Session ID tracking * - GeoIP via timezone mapping * - Vendor-level consent (vendor_consents dict) * - DSR scenarios: Art. 15 Auskunft, Art. 17 Löschung, Art. 20 Portabilität * - Email linking for DSR (device → user mapping) * - Admin modal features (vendor display, withdraw, email linking) */ const API_BASE = process.env.PLAYWRIGHT_API_URL || 'https://macmini:3007/api/sdk/v1/banner' const TENANT_ID = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const HEADERS = { 'Content-Type': 'application/json', 'X-Tenant-ID': TENANT_ID, } const TS = Date.now() const SITE_ID = `e2e-cmp3-${TS}` const DEVICE_FP = `e2e-device-${TS}` // ============================================================================ // 1. Vendor-Agnostic Consent Fields // ============================================================================ test.describe('Vendor-Agnostic Consent Fields', () => { test('should store all 20+ fields on consent', async ({ request }) => { // Create site config first await request.post(`${API_BASE}/admin/sites`, { headers: HEADERS, data: { site_id: SITE_ID, site_name: 'E2E CMP Phase 3', site_url: 'https://test.example.com' }, }) // Record consent with all vendor-agnostic fields const res = await request.post(`${API_BASE}/consent`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: DEVICE_FP, categories: ['essential', 'functional', 'analytics'], vendors: ['Google Analytics', 'Matomo'], vendor_consents: { 'Google Analytics': true, 'Matomo': true, 'Facebook Pixel': false }, user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) E2E-Test', consent_method: 'custom_selection', page_url: 'https://test.example.com/pricing', referrer: 'https://google.com', device_type: 'desktop', browser: 'Chrome/120.0', os: 'Mac OS X 10.15.7', screen_resolution: '1920x1080', consent_scope: 'domain', session_id: 'e2e-session-001', timezone: 'Europe/Berlin', scripts_blocked: [{ src: 'https://connect.facebook.net/fbevents.js', category: 'marketing' }], scripts_released: [{ src: 'https://www.googletagmanager.com/gtag/js', category: 'analytics' }], cookies_set: [ { name: '_ga', domain: '.test.example.com', expiry_days: 730, category: 'analytics' }, { name: 'bp_consent', domain: '.test.example.com', expiry_days: 365, category: 'essential' }, ], }, }) expect(res.status()).toBe(200) const consent = await res.json() expect(consent.id).toBeTruthy() expect(consent.consent_method).toBe('custom_selection') expect(consent.device_type).toBe('desktop') expect(consent.browser).toBe('Chrome/120.0') expect(consent.os).toBe('Mac OS X 10.15.7') expect(consent.page_url).toBe('https://test.example.com/pricing') expect(consent.session_id).toBe('e2e-session-001') expect(consent.geo_country).toBe('DE') // Europe/Berlin → DE expect(consent.scripts_released).toHaveLength(1) expect(consent.cookies_set).toHaveLength(2) expect(consent.vendor_consents).toEqual({ 'Google Analytics': true, 'Matomo': true, 'Facebook Pixel': false }) }) test('should update consent on same fingerprint (upsert)', async ({ request }) => { const res = await request.post(`${API_BASE}/consent`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: DEVICE_FP, categories: ['essential'], // changed from all 3 to essential only vendors: [], consent_method: 'reject_all', page_url: 'https://test.example.com/settings', timezone: 'Europe/Vienna', }, }) expect(res.status()).toBe(200) const consent = await res.json() expect(consent.consent_method).toBe('reject_all') expect(consent.geo_country).toBe('AT') // Europe/Vienna → AT expect(consent.categories).toEqual(['essential']) }) }) // ============================================================================ // 2. DSR Scenarios — Art. 15 Auskunft // ============================================================================ test.describe('DSR — Art. 15 Auskunftsrecht', () => { const DSR_EMAIL = `dsr-user-${TS}@example.com` const DSR_DEVICE_1 = `dsr-desktop-${TS}` const DSR_DEVICE_2 = `dsr-mobile-${TS}` test.beforeAll(async ({ request }) => { // Scenario: User visited website from 2 devices, then linked their email // Device 1: Desktop consent await request.post(`${API_BASE}/consent`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: DSR_DEVICE_1, categories: ['essential', 'analytics'], consent_method: 'accept_all', device_type: 'desktop', browser: 'Firefox/121.0', page_url: 'https://test.example.com/', timezone: 'Europe/Berlin', }, }) // Device 2: Mobile consent await request.post(`${API_BASE}/consent`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: DSR_DEVICE_2, categories: ['essential'], consent_method: 'reject_all', device_type: 'mobile', browser: 'Safari/17.0', page_url: 'https://test.example.com/pricing', timezone: 'Europe/Berlin', }, }) // User logs in and links email to both devices await request.post(`${API_BASE}/consent/link-email`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: DSR_DEVICE_1, email: DSR_EMAIL }, }) await request.post(`${API_BASE}/consent/link-email`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: DSR_DEVICE_2, email: DSR_EMAIL }, }) }) test('Art. 15 — should find all consents by email', async ({ request }) => { const res = await request.get(`${API_BASE}/consent/by-email/${DSR_EMAIL}`, { headers: HEADERS }) expect(res.status()).toBe(200) const consents = await res.json() expect(consents).toHaveLength(2) expect(consents.map((c: { device_fingerprint: string }) => c.device_fingerprint).sort()).toEqual( [DSR_DEVICE_1, DSR_DEVICE_2].sort() ) }) test('Art. 15/20 — should export all consent data for DSR', async ({ request }) => { const res = await request.get(`${API_BASE}/consent/dsr-export/${DSR_EMAIL}`, { headers: HEADERS }) expect(res.status()).toBe(200) const exportData = await res.json() expect(exportData.consents).toHaveLength(2) expect(exportData.audit_trail.length).toBeGreaterThan(0) }) test('Art. 17 — should delete all consents by email (erasure)', async ({ request }) => { const res = await request.delete(`${API_BASE}/consent/by-email/${DSR_EMAIL}`, { headers: HEADERS }) expect(res.status()).toBe(200) const result = await res.json() expect(result.deleted_count).toBe(2) // Verify deletion const check = await request.get(`${API_BASE}/consent/by-email/${DSR_EMAIL}`, { headers: HEADERS }) const remaining = await check.json() expect(remaining).toHaveLength(0) }) }) // ============================================================================ // 3. DSR Scenarios — Cookie Banner User (anonymous) // ============================================================================ test.describe('DSR — Anonymous Cookie Banner User', () => { const ANON_DEVICE = `anon-user-${TS}` test.beforeAll(async ({ request }) => { await request.post(`${API_BASE}/consent`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: ANON_DEVICE, categories: ['essential', 'functional'], consent_method: 'custom_selection', device_type: 'tablet', browser: 'Chrome/120.0', }, }) }) test('should export consent by device fingerprint', async ({ request }) => { const res = await request.get( `${API_BASE}/consent/export?site_id=${SITE_ID}&device_fingerprint=${ANON_DEVICE}`, { headers: HEADERS } ) expect(res.status()).toBe(200) const data = await res.json() expect(data.device_fingerprint).toBe(ANON_DEVICE) expect(data.consents).toHaveLength(1) expect(data.audit_trail.length).toBeGreaterThan(0) }) test('should withdraw consent by ID', async ({ request }) => { // Get consent ID first const getRes = await request.get( `${API_BASE}/consent?site_id=${SITE_ID}&device_fingerprint=${ANON_DEVICE}`, { headers: HEADERS } ) const { consent } = await getRes.json() expect(consent).toBeTruthy() // Withdraw const delRes = await request.delete(`${API_BASE}/consent/${consent.id}`, { headers: HEADERS }) expect(delRes.status()).toBe(200) // Verify const checkRes = await request.get( `${API_BASE}/consent?site_id=${SITE_ID}&device_fingerprint=${ANON_DEVICE}`, { headers: HEADERS } ) const result = await checkRes.json() expect(result.has_consent).toBe(false) }) }) // ============================================================================ // 4. DSR Scenarios — Login User (Customer) who also used Cookie Banner // ============================================================================ test.describe('DSR — Customer with Banner + Login', () => { const CUSTOMER_EMAIL = `customer-${TS}@company.com` const CUSTOMER_DEVICE = `customer-device-${TS}` test('full lifecycle: consent → login → link → Art.15 → Art.17', async ({ request }) => { // Step 1: Anonymous visit → cookie consent const consentRes = await request.post(`${API_BASE}/consent`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: CUSTOMER_DEVICE, categories: ['essential', 'analytics'], consent_method: 'accept_all', device_type: 'desktop', browser: 'Edge/120.0', page_url: 'https://test.example.com/', timezone: 'Europe/Zurich', scripts_released: [{ src: 'https://cdn.matomo.cloud/test.js', category: 'analytics' }], cookies_set: [{ name: '_pk_id', domain: '.test.example.com', expiry_days: 393, category: 'analytics' }], }, }) expect(consentRes.status()).toBe(200) const consent = await consentRes.json() expect(consent.geo_country).toBe('CH') // Europe/Zurich → CH // Step 2: Customer logs in → email linked const linkRes = await request.post(`${API_BASE}/consent/link-email`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: CUSTOMER_DEVICE, email: CUSTOMER_EMAIL }, }) expect(linkRes.status()).toBe(200) // Step 3: Art. 15 — Customer requests their data const exportRes = await request.get(`${API_BASE}/consent/dsr-export/${CUSTOMER_EMAIL}`, { headers: HEADERS }) expect(exportRes.status()).toBe(200) const exportData = await exportRes.json() expect(exportData.consents.length).toBeGreaterThan(0) expect(exportData.audit_trail.length).toBeGreaterThan(0) // Verify export contains all consent details const exported = exportData.consents[0] expect(exported.categories).toContain('analytics') expect(exported.linked_email).toBe(CUSTOMER_EMAIL) // Step 4: Art. 17 — Customer requests erasure const deleteRes = await request.delete(`${API_BASE}/consent/by-email/${CUSTOMER_EMAIL}`, { headers: HEADERS }) expect(deleteRes.status()).toBe(200) // Step 5: Verify complete erasure const verifyRes = await request.get(`${API_BASE}/consent/by-email/${CUSTOMER_EMAIL}`, { headers: HEADERS }) const remaining = await verifyRes.json() expect(remaining).toHaveLength(0) }) }) // ============================================================================ // 5. Admin Dashboard Integration // ============================================================================ test.describe('Admin Dashboard — Consent Management', () => { const ADMIN_DEVICE = `admin-test-${TS}` test.beforeAll(async ({ request }) => { await request.post(`${API_BASE}/consent`, { headers: HEADERS, data: { site_id: SITE_ID, device_fingerprint: ADMIN_DEVICE, categories: ['essential', 'functional', 'analytics'], vendors: ['Matomo'], vendor_consents: { Matomo: true }, consent_method: 'accept_all', device_type: 'desktop', browser: 'Chrome/121.0', os: 'Windows NT 10.0', screen_resolution: '2560x1440', page_url: 'https://test.example.com/dashboard', session_id: 'admin-session-001', timezone: 'Europe/Berlin', }, }) }) test('should list consents with new fields', async ({ request }) => { const res = await request.get(`${API_BASE}/admin/consents?site_id=${SITE_ID}`, { headers: HEADERS }) expect(res.status()).toBe(200) const data = await res.json() expect(data.total).toBeGreaterThan(0) const consent = data.consents.find((c: { device_fingerprint: string }) => c.device_fingerprint === ADMIN_DEVICE) expect(consent).toBeTruthy() expect(consent.consent_method).toBe('accept_all') expect(consent.device_type).toBe('desktop') expect(consent.browser).toBe('Chrome/121.0') expect(consent.os).toBe('Windows NT 10.0') expect(consent.screen_resolution).toBe('2560x1440') expect(consent.session_id).toBe('admin-session-001') expect(consent.geo_country).toBe('DE') expect(consent.vendor_consents).toEqual({ Matomo: true }) }) test('should show site stats with category acceptance', async ({ request }) => { const res = await request.get(`${API_BASE}/admin/stats/${SITE_ID}`, { headers: HEADERS }) expect(res.status()).toBe(200) const stats = await res.json() expect(stats.total_consents).toBeGreaterThan(0) expect(stats.category_acceptance).toBeTruthy() expect(stats.category_acceptance.essential).toBeTruthy() expect(stats.category_acceptance.essential.rate).toBeGreaterThan(0) }) }) // ============================================================================ // 6. Cleanup // ============================================================================ test.describe('Cleanup', () => { test('should delete test site config', async ({ request }) => { const res = await request.delete(`${API_BASE}/admin/sites/${SITE_ID}`, { headers: HEADERS }) expect(res.status()).toBe(204) }) })