# 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.