# ADR-001: PHPUnit-Integration in CI + Pre-Commit-Hook

**Status:** Accepted (nachträglich dokumentiert)
**Datum:** 2026-06-04
**Issues:** #65, #67
**PRs:** #69, #70
**Author:** Hermes (nachträgliche Doku auf Martin-Anweisung)

## Kontext

Das Projekt `landingpage-haus-schleusingen` enthält 18 PHPUnit-Tests (31 Assertions, 100% Pass-Rate) im `tests/`-Verzeichnis. Vor diesem ADR gab es:

- **Lokale Test-Verifikation** nur manuell (`vendor/bin/phpunit`)
- **Keine CI-Pipeline** — Tests liefen nicht automatisch bei Push/PR
- **Pre-Commit-Hook** deckte nur Linting (PHP-Syntax, HTML, CSS, JS, Prettier), keine Tests

**Probleme:**

1. **Refactoring-Risiko:** Ohne CI-Tests können Bugs bei zukünftigen Änderungen unentdeckt auf `main` landen
2. **Regressions:** Kein Schutz gegen versehentliches Brechen existierender Tests
3. **Code-Qualität:** Manuelle Test-Verifikation ist fehleranfällig (vergessen, übersprungen)
4. **Reviewer-Belastung:** Martin muss bei jedem PR manuell Tests laufen lassen

**Anforderungen:**

- Tests müssen **automatisch** bei jedem Push/PR laufen
- Tests müssen **lokal vor dem Commit** laufen (schneller Feedback-Loop)
- Bei Test-Fehler: **Commit/PR abbrechen** mit klarer Fehlermeldung
- **Performance:** Tests dürfen nicht bei CSS/HTML/JS-only-Änderungen laufen (false-positive-Friktion vermeiden)
- **DRY:** Eine einzige Test-Ausführungslogik für lokale + CI-Ausführung

## Entscheidung

**Zwei-Layer-Strategie:** CI-Pipeline (Remote-Verifikation) + Pre-Commit-Hook (Lokal vor Push).

### Layer 1: CI-Pipeline (`.gitea/workflows/phpunit.yml`)

- **Trigger:** `push` + `pull_request` auf `main`
- **Runtime:** `ubuntu-latest`, PHP 8.5 + Composer
- **Install:** `apt-get install -y php-cli composer php-xml php-mbstring`
- **Test:** `composer install` (Lazy: nur wenn `vendor/` fehlt) → `vendor/bin/phpunit`
- **Architektur:** Analog zu existierender `lint.yml`, eigenständige Pipeline (nicht mit Lint kombiniert, da Test-Laufzeit ~25s unabhängig von Lint)

### Layer 2: Pre-Commit-Hook (`.husky/pre-commit` + `scripts/pre-commit-checks.sh`)

- **Trigger:** Lokaler `git commit` (Husky 9 Standard)
- **PHP_Detection:** `git diff --cached --name-only | grep -E '\.(php)$|^phpunit\.xml$|^composer\.(json|lock)$'`
- **Bei PHP-Files:** `scripts/pre-commit-checks.sh` ausführen
- **Bei Non-PHP-Commits:** PHPUnit skippen (Performance)
- **Bei Test-Fehler:** Exit-Code != 0 → Husky bricht Commit ab
- **DRY:** Shared `scripts/pre-commit-checks.sh` wird auch von `scripts/safe-commit.sh` aufgerufen (AI-Agent-Bypass-Schutz)

### Schichten-Logik

```
┌─────────────────────────────────────┐
│ Git Commit (lokal)                  │
│   ↓                                 │
│ Husky Pre-Commit Hook               │
│   ↓                                 │
│ scripts/pre-commit-checks.sh        │ ← Eine Source-of-Truth
│   ├─ Lint (PHP, HTML, CSS, JS)      │
│   └─ PHPUnit (wenn PHP touched)     │
│       ├─ composer install (lazy)    │
│       └─ vendor/bin/phpunit         │
│   ↓ (Exit 0)                        │
│ Commit erstellt                     │
│   ↓                                 │
│ git push → Gitea                    │
│   ↓                                 │
│ .gitea/workflows/phpunit.yml        │ ← CI-Verifikation
│   ├─ PHP + Composer install         │
│   └─ vendor/bin/phpunit             │
│   ↓ (Exit 0)                        │
│ PR mergeable                        │
└─────────────────────────────────────┘
```

## Konsequenzen

### Positiv

- **Doppelte Absicherung:** Lokal (schnell) + CI (authoritativ)
- **Frühes Feedback:** Entwickler merkt sofort bei `git commit` statt erst nach Push
- **Performance:** Non-PHP-Commits (CSS, HTML, JS, Markdown) lösen keinen PHPUnit-Run aus
- **Wartbarkeit:** Single-Source-of-Truth (`scripts/pre-commit-checks.sh`) — Hook und safe-commit.sh synchron
- **CI-Laufzeit:** ~25s für 18 Tests, akzeptabel für Standard-Pipeline
- **Audit-Kette:** Issue → PR → Merge → autom. Issue-Close bleibt sauber

### Negativ

- **Wartungs-Overhead:** Bei neuen Test-Dateien (z.B. `tests/Integration/`) muss `phpunit.xml` ggf. angepasst werden
- **Pre-Commit-Delay:** Bei PHP-Commits ~5-10s lokaler Test-Lauf (akzeptabel, schneller als CI-Round-Trip)
- **Composer-Install-Falle:** Bei fehlendem `vendor/` wird `composer install` ausgeführt — potenziell langsam beim ersten Commit in neuem Clone
- **Bypass-Pfad:** `git commit --no-verify` überspringt Hook (per Design, aber Risiko)
- **Schutz gegen Bypass:** `scripts/safe-commit.sh` ruft `pre-commit-checks.sh` direkt auf (auch bei `--no-verify` würde der Bypass hier nicht greifen, da `safe-commit.sh` das Script direkt invoked)

### Risiken

- **PHP-Versions-Drift:** CI läuft auf PHP 8.5, lokal möglicherweise älter. Mitigation: `phpunit.xml` schema-konform, keine PHP-8.5-spezifischen Features in Tests
- **Test-Datenbank:** Aktuell keine DB-Tests, aber bei zukünftigen Integration-Tests muss SQLite-in-memory oder Test-Fixture sichergestellt werden
- **Composer-Versions-Drift:** CI nutzt neueste Composer-Version, lokal ggf. älter → `composer.lock` muss gepflegt sein
- **Stale-Index-Edge-Case:** `git add file.php; rm file.php; git commit` würde PHPUnit gegen veraltete staged-Version laufen lassen. Mitigation: Stale-Index-Safety-Check in `pre-commit-checks.sh` prüft Disk-Existenz aller gestaged PHP-Files

## Alternativen (verworfen)

### Alternative A: PHPUnit NUR in CI, kein Pre-Commit-Hook

- **Pro:** Einfacher, kein lokaler Overhead
- **Pro:** Bypass unmöglich (`--no-verify` irrelevant)
- **Contra:** Feedback-Loop erst nach Push (30s+)
- **Contra:** Martin muss auf CI warten statt sofort beim Commit zu sehen
- **Verworfen weil:** Schnelleres Feedback-Loop wichtiger als Einfachheit

### Alternative B: PHPUnit NUR lokal, keine CI-Pipeline

- **Pro:** Schnellste lokale Feedback-Loop
- **Pro:** Keine CI-Infrastruktur nötig
- **Contra:** Kein Schutz vor `git commit --no-verify`-Bypass
- **Contra:** Kein Schutz vor ungetesteten Pushes direkt auf main
- **Verworfen weil:** CI-Protection vor versehentlichen Pushes essentiell

### Alternative C: PHPUnit mit `npm test` statt direkter `vendor/bin/phpunit`

- **Pro:** Konsistenz mit existierendem `npm run lint`-Pattern
- **Pro:** Lint + Test in einem Schritt möglich
- **Contra:** Zusätzlicher npm-Wrapper-Layer, Overhead
- **Contra:** PHP-Files würden trotzdem in npm-Skript laufen (unidiomatisch)
- **Verworfen weil:** Direkter `vendor/bin/phpunit` ist PHP-idiomatisch, klarer

### Alternative D: PHPUnit in bestehende `lint.yml` integrieren

- **Pro:** Weniger Workflow-Files
- **Contra:** Lint- und Test-Stage schwerer zu trennen
- **Contra:** Lint-Pipeline bricht bei Test-Fehler, obwohl Lint sauber ist
- **Verworfen weil:** Trennung der Verantwortlichkeiten (Lint = Syntax, Test = Verhalten)

## Auswirkungen

### Performance

- **CI-Pipeline:** ~25s für 18 Tests, akzeptabel
- **Pre-Commit-Local:** ~5-10s bei PHP-Commits, <1s bei Non-PHP-Commits (Skip)
- **Composer-Install:** ~3-5s beim ersten Run nach Clone, dann Cache-Hit

### Security

- Keine Secrets in Test-Files
- Keine externen API-Calls in Tests
- Keine Production-DB-Zugriffe (alle Tests in-memory oder mit Test-Fixtures)

### Testbarkeit

- Bestehende Tests bleiben unverändert (TDD-konform: 18 Tests, 31 Assertions)
- PHPUnit-Konfiguration in `phpunit.xml`
- Test-Layout: `tests/Core/RouterTest.php` (Namespaces-Pattern `App\Tests\`)

### Migrationspfad

- Keine Migration nötig — additive Änderung
- Bestehende Commits funktionieren unverändert
- Hook aktiviert sich automatisch bei `npm install` (Husky 9 Standard)
- CI-Pipeline triggert beim ersten Push nach Merge

### Rollback

- **CI:** `.gitea/workflows/phpunit.yml` löschen → keine CI-Tests mehr
- **Pre-Commit:** `.husky/pre-commit` revertieren + `scripts/pre-commit-checks.sh` löschen
- **Atomic:** Jede Schicht unabhängig deaktivierbar

## Verwandte Entscheidungen

- ADR-002 (offen): Stylelint-Pattern-Fix für `./public/css/**/*.css` (Folge-Bug entdeckt beim Test der Pipeline)
- ADR-003 (offen): act_runner-Docker-Orchestrierung-Workaround (v0.6.1 startet keine Container)

## Nachträgliche Dokumentation

Dieser ADR wird **nachträglich** erstellt (Code-Phase bereits abgeschlossen, PRs gemerged), um:

1. Die Architektur-Entscheidung für die Nachwelt festzuhalten
2. Den bewussten 2-Layer-Ansatz (lokal + CI) zu begründen
3. Als Template für zukünftige Test-Integrationen in anderen Projekten zu dienen

**Lesson Learned:** ADRs sollten VOR der Code-Phase erstellt werden (Forward-Engineering). Nachträgliche Doku ist besser als keine, aber ein Auditor würde die Entscheidungs-Spur zwischen Issue-Erstellung und Implementation schwer nachvollziehen können. Für die nächsten Issues: ADR in Ph0.5 verbindlich, nicht erst in Ph8.
