6f58fdbaa5
3 test levels: Real-World Benchmarks (10 DE websites), Adversarial Tests (30 tricky cases), Regression Harness (CI/CD quality gate). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
336 lines
10 KiB
Markdown
336 lines
10 KiB
Markdown
# Instruktion: Teststrategie Block C
|
|
|
|
**Repo:** `/Users/benjaminadmin/Projekte/breakpilot-core/`
|
|
**Verzeichnis:** `control-pipeline/tests/`
|
|
**Erstellt:** 2026-05-01
|
|
**Geschaetzter Aufwand:** 2-3 Tage
|
|
|
|
## Ausgangslage
|
|
|
|
- 221 bestehende Tests in 7 Dateien (NICHT aendern!)
|
|
- 40 Golden Test Cases (golden_controls.yaml)
|
|
- 24 Demo Cases (demo_cases.yaml)
|
|
- Alle Tests sind pure Python, kein DB noetig
|
|
- Pipeline v1 abgeschlossen: 151.675 unique Controls, 15.291 Dependencies
|
|
|
|
## Aufgabe 1: Real-World Benchmarks (C1)
|
|
|
|
### Was zu tun ist
|
|
|
|
10 echte deutsche E-Commerce Websites manuell pruefen und Ground Truth YAML erstellen.
|
|
|
|
### Verzeichnis
|
|
|
|
```
|
|
control-pipeline/tests/benchmarks/
|
|
├── amazon_de.yaml
|
|
├── zalando_de.yaml
|
|
├── otto_de.yaml
|
|
├── lidl_de.yaml
|
|
├── check24_de.yaml
|
|
├── booking_de.yaml
|
|
├── thomann_de.yaml
|
|
├── aboutyou_de.yaml
|
|
├── mytheresa_com.yaml
|
|
└── kleiner_shop.yaml
|
|
```
|
|
|
|
### Format pro Website
|
|
|
|
```yaml
|
|
website: amazon.de
|
|
url: https://www.amazon.de
|
|
checked_at: "2026-05-XX"
|
|
checked_by: "Name"
|
|
|
|
ground_truth:
|
|
impressum:
|
|
present: true/false
|
|
complete: true/false # Name, Adresse, Email, HR-Nummer, USt-ID
|
|
within_2_clicks: true/false
|
|
missing_fields: [] # z.B. ["USt-ID", "Handelsregister"]
|
|
|
|
datenschutzerklaerung:
|
|
present: true/false
|
|
art13_complete: true/false
|
|
missing_art13_fields: [] # z.B. ["Speicherdauer", "Empfaenger"]
|
|
rechtsgrundlagen_korrekt: true/false
|
|
wrong_legal_bases: [] # z.B. ["Analytics auf lit. f statt lit. a"]
|
|
|
|
cookie_banner:
|
|
present: true/false
|
|
reject_equally_easy: true/false # CNIL: Ablehnen = gleich prominent
|
|
cookies_before_consent: true/false # Planet49: Cookies VOR Consent?
|
|
dark_patterns: [] # z.B. ["Ablehnen-Button kleiner", "Ablehnen hinter Einstellungen"]
|
|
|
|
widerrufsbelehrung:
|
|
present: true/false
|
|
matches_legal_template: true/false # Gesetzliches Muster
|
|
|
|
agb:
|
|
present: true/false
|
|
checkout_button_text: "..." # z.B. "Jetzt kaufen" (korrekt) vs "Weiter" (falsch)
|
|
|
|
google_fonts_external: true/false
|
|
google_analytics: true/false
|
|
|
|
third_party_services:
|
|
- name: "Google Analytics"
|
|
detected: true
|
|
consent_required: true
|
|
consent_obtained_before_load: false
|
|
- name: "Facebook Pixel"
|
|
detected: true
|
|
consent_required: true
|
|
consent_obtained_before_load: false
|
|
|
|
expected_findings:
|
|
- "Cookie-Banner: Ablehnen nicht gleichwertig"
|
|
- "Google Analytics ohne vorherige Einwilligung"
|
|
- "DSE: Rechtsgrundlage fuer Analytics falsch"
|
|
|
|
expected_no_findings:
|
|
- "Impressum fehlt" # Ist vorhanden, darf nicht geflagt werden
|
|
```
|
|
|
|
### Test-Runner
|
|
|
|
```python
|
|
# control-pipeline/tests/test_benchmarks.py
|
|
"""
|
|
Real-World Benchmark Tests — vergleicht Agent-Findings mit manueller Ground Truth.
|
|
Erfordert: Compliance Agent muss laufen (https://macmini:3007/sdk/agent)
|
|
"""
|
|
|
|
import yaml
|
|
import pytest
|
|
import os
|
|
|
|
BENCHMARK_DIR = os.path.join(os.path.dirname(__file__), "benchmarks")
|
|
|
|
def load_benchmarks():
|
|
cases = []
|
|
for f in sorted(os.listdir(BENCHMARK_DIR)):
|
|
if f.endswith(".yaml"):
|
|
with open(os.path.join(BENCHMARK_DIR, f)) as fh:
|
|
cases.append(yaml.safe_load(fh))
|
|
return cases
|
|
|
|
class TestBenchmarks:
|
|
"""Precision/Recall gegen Ground Truth messen."""
|
|
|
|
@pytest.mark.parametrize("case", load_benchmarks(), ids=lambda c: c["website"])
|
|
def test_benchmark(self, case):
|
|
# TODO: Agent gegen Website laufen lassen
|
|
# TODO: Findings mit expected_findings vergleichen
|
|
# TODO: Precision + Recall berechnen
|
|
pass
|
|
```
|
|
|
|
### Wie die Ground Truth erstellt wird
|
|
|
|
1. Website im Browser oeffnen
|
|
2. Impressum pruefen (alle Pflichtfelder nach § 5 DDG)
|
|
3. Datenschutzerklaerung lesen (Art. 13 DSGVO Checkliste)
|
|
4. Cookie-Banner testen (Ablehnen gleich einfach? Cookies vor Consent?)
|
|
5. Widerrufsbelehrung gegen gesetzliches Muster pruefen
|
|
6. Browser DevTools: Netzwerk-Tab → externe Requests vor Consent?
|
|
7. Alles in YAML dokumentieren
|
|
|
|
**Ziel-Metriken:**
|
|
- Precision > 80% (wenige False Positives)
|
|
- Recall > 70% (findet die meisten echten Probleme)
|
|
|
|
---
|
|
|
|
## Aufgabe 2: Adversarial Tests (C2)
|
|
|
|
### Was zu tun ist
|
|
|
|
30 tricky Test Cases erstellen die den Agent/Controls herausfordern.
|
|
|
|
### Datei
|
|
|
|
`control-pipeline/tests/adversarial_cases.yaml`
|
|
|
|
### Kategorien
|
|
|
|
**A. Falsche Rechtsgrundlage (8 Cases):**
|
|
- Analytics auf lit. f statt lit. a
|
|
- Marketing-Emails auf lit. b statt lit. a
|
|
- Mitarbeiter-Tracking auf lit. f statt Betriebsvereinbarung
|
|
- Biometrische Daten auf lit. f statt Art. 9
|
|
- Profiling auf lit. f statt Art. 22
|
|
- Newsletter auf lit. b statt lit. a
|
|
- Social Login auf lit. b statt lit. a
|
|
- Kreditscoring auf lit. f statt lit. a + Art. 22
|
|
|
|
**B. Dark Patterns (6 Cases):**
|
|
- Ablehnen-Button existiert aber 3px gross + grau
|
|
- "Alle akzeptieren" prominent, "Einstellungen" statt "Ablehnen"
|
|
- Cookie-Wall: Inhalt erst nach Zustimmung sichtbar
|
|
- Vorausgefuellte Checkboxen (Planet49)
|
|
- Confirm-Shaming: "Nein, ich moechte keine sichere Verbindung"
|
|
- Ablehnen erfordert 3 Klicks, Akzeptieren nur 1
|
|
|
|
**C. Fast-vollstaendige Dokumente (6 Cases):**
|
|
- Impressum komplett bis auf USt-ID
|
|
- DSE ohne Speicherdauer
|
|
- DSE ohne DSB-Kontakt
|
|
- Widerrufsbelehrung mit falschem Fristbeginn
|
|
- AGB ohne Gerichtsstand
|
|
- Cookie-Policy ohne Auflistung aller Cookies
|
|
|
|
**D. Semantisch aehnlich aber verschieden (5 Cases):**
|
|
- "Admin-MFA" vs "User-MFA" (verschiedene Scopes!)
|
|
- "Daten loeschen nach Kuendigung" vs "Daten loeschen nach Aufbewahrungsfrist"
|
|
- "Rate Limiting API" vs "Rate Limiting Login"
|
|
- "Verschluesselung at rest" vs "Verschluesselung in transit"
|
|
- "Incident Response Plan" vs "Business Continuity Plan"
|
|
|
|
**E. Semantisch verschieden aber gleich klingend (5 Cases):**
|
|
- "Einwilligung" (DSGVO) vs "Einwilligung" (Werbung)
|
|
- "Verarbeitung" (Daten) vs "Verarbeitung" (Lebensmittel)
|
|
- "Risikobewertung" (DSGVO DSFA) vs "Risikobewertung" (Finanzrisiko)
|
|
- "Audit" (Datenschutz) vs "Audit" (Finanzen)
|
|
- "Zertifizierung" (ISO 27001) vs "Zertifizierung" (CE-Marking)
|
|
|
|
### Format
|
|
|
|
```yaml
|
|
- id: ADV-LIT-001
|
|
category: wrong_legal_basis
|
|
input: "Wir verarbeiten Ihre Daten fuer Webanalyse auf Grundlage unseres berechtigten Interesses (Art. 6 Abs. 1 lit. f DSGVO)."
|
|
context: "DSE-Abschnitt ueber Google Analytics"
|
|
expected:
|
|
finding: true
|
|
finding_type: "wrong_legal_basis"
|
|
correct_basis: "Art. 6 Abs. 1 lit. a (Einwilligung)"
|
|
reason: "Analytics erfordert Einwilligung, nicht berechtigtes Interesse (EuGH C-673/17 Planet49)"
|
|
difficulty: medium # easy / medium / hard
|
|
```
|
|
|
|
---
|
|
|
|
## Aufgabe 3: Regression-Harness (C3)
|
|
|
|
### Was zu tun ist
|
|
|
|
1. `conftest.py` mit shared Fixtures
|
|
2. `test_regression.py` mit Snapshot-Tests
|
|
3. CI/CD Quality Gate
|
|
|
|
### conftest.py
|
|
|
|
```python
|
|
# control-pipeline/tests/conftest.py
|
|
import os
|
|
import pytest
|
|
|
|
@pytest.fixture(scope="session")
|
|
def db_session():
|
|
"""DB session for integration tests — skip if no DATABASE_URL."""
|
|
url = os.getenv("DATABASE_URL")
|
|
if not url:
|
|
pytest.skip("DATABASE_URL not set")
|
|
from db.session import SessionLocal
|
|
db = SessionLocal()
|
|
yield db
|
|
db.close()
|
|
|
|
@pytest.fixture
|
|
def sample_controls(db_session):
|
|
"""Load 100 random draft controls for regression testing."""
|
|
from sqlalchemy import text
|
|
rows = db_session.execute(text("""
|
|
SELECT control_id, title, category, severity,
|
|
generation_metadata->>'assertion' as assertion
|
|
FROM compliance.canonical_controls
|
|
WHERE release_state = 'draft' AND decomposition_method = 'pass0b'
|
|
ORDER BY random() LIMIT 100
|
|
""")).fetchall()
|
|
return [dict(r._mapping) for r in rows]
|
|
```
|
|
|
|
### test_regression.py
|
|
|
|
```python
|
|
# control-pipeline/tests/test_regression.py
|
|
"""
|
|
Regression Tests — pruefen ob Pipeline-Updates bestehende Controls veraendern.
|
|
Erfordert: DATABASE_URL Umgebungsvariable
|
|
"""
|
|
|
|
class TestControlStability:
|
|
def test_draft_count_stable(self, db_session):
|
|
"""Draft count darf nicht um >5% abweichen."""
|
|
from sqlalchemy import text
|
|
count = db_session.execute(text(
|
|
"SELECT COUNT(*) FROM compliance.canonical_controls "
|
|
"WHERE release_state = 'draft' AND decomposition_method = 'pass0b'"
|
|
)).scalar()
|
|
assert count > 140000, f"Draft count too low: {count}"
|
|
assert count < 200000, f"Draft count too high: {count}"
|
|
|
|
def test_no_null_assertions(self, db_session):
|
|
"""Alle draft Controls muessen eine assertion haben."""
|
|
from sqlalchemy import text
|
|
null_count = db_session.execute(text(
|
|
"SELECT COUNT(*) FROM compliance.canonical_controls "
|
|
"WHERE release_state = 'draft' AND decomposition_method = 'pass0b' "
|
|
"AND (generation_metadata->>'assertion' IS NULL OR generation_metadata->>'assertion' = '')"
|
|
)).scalar()
|
|
assert null_count < 1000, f"Too many controls without assertion: {null_count}"
|
|
|
|
def test_dependency_graph_valid(self, db_session):
|
|
"""Keine Zyklen im Dependency-Graph."""
|
|
from sqlalchemy import text
|
|
cycle_count = db_session.execute(text(
|
|
"SELECT COUNT(*) FROM compliance.control_dependencies WHERE is_active = true"
|
|
)).scalar()
|
|
assert cycle_count > 10000, f"Too few dependencies: {cycle_count}"
|
|
|
|
class TestQualityGates:
|
|
def test_duplicate_rate(self, db_session):
|
|
pass # Implementieren: duplicate_rate < 5%
|
|
|
|
def test_evidence_leak_rate(self, db_session):
|
|
pass # Implementieren: evidence_leak < 2%
|
|
```
|
|
|
|
### CI/CD Quality Gate
|
|
|
|
```yaml
|
|
# .gitea/workflows/quality-gate.yml
|
|
name: Control Pipeline Quality Gate
|
|
on:
|
|
push:
|
|
paths:
|
|
- 'control-pipeline/**'
|
|
|
|
jobs:
|
|
quality-gate:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Run Tests
|
|
run: |
|
|
cd control-pipeline
|
|
pip install -r requirements.txt pytest pyyaml
|
|
PYTHONPATH=. pytest tests/ -v --tb=short -x
|
|
- name: Quality Metrics
|
|
run: |
|
|
# Nur wenn Container laeuft
|
|
curl -sf http://127.0.0.1:8098/v1/canonical/generate/quality-metrics || echo "Pipeline not running, skip metrics"
|
|
```
|
|
|
|
---
|
|
|
|
## WICHTIG
|
|
|
|
- Bestehende 221 Tests NICHT aendern
|
|
- NICHT deployen (Container nicht neustarten)
|
|
- Alle neuen Tests muessen ohne DB laufen (ausser test_regression.py mit skip-Marker)
|
|
- Ground Truth YAML manuell erstellen (kein LLM fuer die Referenzdaten!)
|
|
- Bei Fragen: Memory lesen unter `/Users/benjaminadmin/.claude/projects/-Users-benjaminadmin-Projekte-breakpilot-core/memory/`
|