feat: Phase 11 — granular cookie category testing

Tests each consent category in isolation:
- Phase D: Only "Statistics" enabled → checks if only analytics loads
- Phase E: Only "Marketing" enabled → checks if only ads load
- Phase F: Only "Functional" enabled → checks no tracking loads

CMP-specific category selectors for Cookiebot, OneTrust, Usercentrics,
Didomi. Generic fallback via toggle/checkbox keyword detection.

SERVICE_CATEGORY_MAP maps 35+ services to expected categories.
Violations: "Facebook Pixel loads with only Statistics enabled" = miscategorization.

Frontend: category test results shown below Phase A-C with
per-category violation cards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-01 21:15:23 +02:00
parent f6536e8d08
commit 6864849115
4 changed files with 358 additions and 1 deletions
@@ -31,7 +31,15 @@ interface ConsentData {
high: number
undocumented: number
total_violations: number
category_violations?: number
categories_tested?: number
}
category_tests?: {
category: string
category_label: string
tracking_services: string[]
violations: { service: string; severity: string; text: string }[]
}[]
}
const SEV = {
@@ -154,6 +162,39 @@ export function ConsentTestResult({ data }: { data: ConsentData }) {
)}
</div>
{/* Category Tests (Phase D-F) */}
{data.category_tests && data.category_tests.length > 0 && (
<div className="space-y-3">
<h4 className="text-sm font-semibold text-gray-900 mt-2">Kategorie-Tests ({data.category_tests.length})</h4>
{data.category_tests.map((ct, i) => {
const hasViolations = ct.violations.length > 0
return (
<div key={i} className={`border rounded-lg p-4 ${hasViolations ? 'border-red-200 bg-red-50' : 'border-green-200 bg-green-50'}`}>
<h4 className="text-sm font-semibold text-gray-900 mb-2 flex items-center gap-2">
<span>🔀</span> Nur &quot;{ct.category_label}&quot;
</h4>
{ct.violations.length > 0 ? (
ct.violations.map((v, vi) => (
<div key={vi} className="mb-2 p-2 rounded border border-red-300 bg-red-100">
<span className="text-xs font-bold text-red-800 px-1.5 py-0.5 rounded bg-red-200">FALSCH</span>
<span className="text-xs text-red-700 ml-2">{v.text}</span>
</div>
))
) : (
<div className="text-xs text-green-700">
{ct.tracking_services.length > 0 ? (
ct.tracking_services.map((s, si) => <div key={si}> {s} korrekte Kategorie</div>)
) : (
<div> Keine Tracking-Dienste geladen korrekt</div>
)}
</div>
)}
</div>
)
})}
</div>
)}
{/* No banner warning */}
{!data.banner_detected && (
<div className="bg-red-50 border border-red-200 rounded-lg p-3 text-xs text-red-700">