# CI/CD Pipeline & Test-System - Entwicklerdokumentation > **Letzte Aktualisierung:** 2026-02-04 > **Status:** Produktiv > **Maintainer:** DevOps Team --- ## Inhaltsverzeichnis 1. [Architektur-Übersicht](#1-architektur-übersicht) 2. [Woodpecker CI Pipeline](#2-woodpecker-ci-pipeline) - 2.5 [Integration Tests](#25-integration-tests) 3. [Test Registry Backend](#3-test-registry-backend) 4. [Datenbank-Schema](#4-datenbank-schema) 5. [Backlog-System](#5-backlog-system) 6. [Frontend Dashboard](#6-frontend-dashboard) 7. [Service-Übersicht](#7-service-übersicht) 8. [Fehlerbehebung](#8-fehlerbehebung) 9. [API-Referenz](#9-api-referenz) --- ## 1. Architektur-Übersicht ### Systemkomponenten ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ ENTWICKLER-WORKFLOW │ │ │ │ git push ──▶ Gitea (macmini:3003) ──▶ Webhook ──▶ Woodpecker CI │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ WOODPECKER CI PIPELINE │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ Go Tests │ │ Python Tests │ │ Node Tests │ │ Report Results │ │ │ │ (consent, │ │ (backend, │ │ (h5p) │ │ (curl → Backend) │ │ │ │ billing, │ │ voice, │ │ │ │ │ │ │ │ school) │ │ klausur) │ │ │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────────┘ │ │ │ │ └────────────────────────────────────────────────────────────│─────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ BACKEND (FastAPI) │ │ │ │ POST /api/tests/ci-result │ │ │ │ │ ├──▶ TestRunDB (Test-Durchläufe) │ │ ├──▶ TestResultDB (Einzelne Tests) │ │ ├──▶ TestServiceStatsDB (Aggregierte Stats) │ │ └──▶ FailedTestBacklogDB (Backlog bei Fehlern) │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ FRONTEND (Next.js) │ │ │ │ Test Dashboard: https://macmini:3002/infrastructure/tests │ │ CI/CD Dashboard: https://macmini:3002/infrastructure/ci-cd │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### Technologie-Stack | Komponente | Technologie | Port | Beschreibung | |------------|-------------|------|--------------| | **CI/CD Server** | Woodpecker CI v3 | 4431 (HTTPS) | Pipeline-Orchestrierung | | **Git Server** | Gitea | 3003 | Repository-Hosting | | **Backend API** | FastAPI (Python) | 8000 | Test Registry, Backlog-Management | | **Datenbank** | PostgreSQL | 5432 | Persistente Speicherung | | **Frontend** | Next.js | 3002 | Admin Dashboard | | **Plattform** | ARM64 (Apple Silicon) | - | Mac Mini M2 | --- ## 2. Woodpecker CI Pipeline ### Konfigurationsdatei **Pfad:** `.woodpecker/main.yml` ### Pipeline-Stages ```yaml # Stage 1: Lint (nur bei PRs) go-lint # golangci-lint für Go Services python-lint # ruff/black für Python # Stage 2: Unit Tests test-go-consent # consent-service (Go) test-go-billing # billing-service (Go) test-go-school # school-service (Go) test-python-backend # backend (Python/pytest) test-python-voice # voice-service inkl. BQAS (Python/pytest) test-python-klausur # klausur-service (Python/pytest) test-nodejs-h5p # h5p-service (Node.js/Jest) # Stage 3: Report report-test-results # Sendet Ergebnisse an Backend API # Stage 4: Build (nur Tags/manuell) build-consent-service build-backend build-voice-service # Stage 5: Deploy (nur manuell) deploy-production ``` ### Test-Step Struktur (Beispiel Go) ```yaml test-go-consent: image: golang:1.23-alpine environment: CGO_ENABLED: "0" commands: - | set -euo pipefail apk add --no-cache jq bash mkdir -p .ci-results # Directory-Check (falls Service nicht existiert) if [ ! -d "consent-service" ]; then echo '{"service":"consent-service","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-consent.json echo "WARNUNG: consent-service Verzeichnis nicht gefunden" exit 0 fi cd consent-service set +e go test -v -json -coverprofile=coverage.out ./... 2>&1 | tee ../.ci-results/test-consent.json TEST_EXIT=$? set -e # jq für korrektes JSON-Parsing (nur Test-Zeilen zählen, nicht Package-Zeilen) TOTAL=$(jq -s '[.[] | select(.Action=="run" and .Test != null)] | length' ../.ci-results/test-consent.json || echo 0) PASSED=$(jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length' ../.ci-results/test-consent.json || echo 0) FAILED=$(jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length' ../.ci-results/test-consent.json || echo 0) SKIPPED=$(jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length' ../.ci-results/test-consent.json || echo 0) COVERAGE=$(go tool cover -func=coverage.out 2>/dev/null | tail -1 | awk '{print $3}' | tr -d '%' || echo "0") [ -z "$COVERAGE" ] && COVERAGE=0 echo "{\"service\":\"consent-service\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":$COVERAGE}" > ../.ci-results/results-consent.json cat ../.ci-results/results-consent.json if [ "$FAILED" -gt "0" ] || [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi ``` **Wichtige Änderungen gegenüber früheren Versionen:** - `set -euo pipefail` für strikte Fehlerbehandlung - `jq` statt `grep -c` für korrektes JSON-Parsing (verhindert Überzählung bei mehreren Actions pro Test) - Directory-Check vor dem `cd` Befehl - Separate Prüfung von `TEST_EXIT` und `FAILED` Count ### Test-Step Struktur (Beispiel Python) ```yaml test-python-backend: image: python:3.12-slim commands: - | mkdir -p .ci-results cd backend pip install --quiet -r requirements.txt pip install --quiet pytest pytest-cov pytest-asyncio pytest-json-report # Tests mit JSON-Report ausführen pytest tests/ -v --tb=short \ --cov=. --cov-report=term-missing \ --json-report --json-report-file=../.ci-results/test-backend.json || true # Statistiken aus JSON extrahieren if [ -f ../.ci-results/test-backend.json ]; then TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-backend.json')); print(d.get('summary',{}).get('total',0))") PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-backend.json')); print(d.get('summary',{}).get('passed',0))") FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-backend.json')); print(d.get('summary',{}).get('failed',0))") SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-backend.json')); print(d.get('summary',{}).get('skipped',0))") else TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi # JSON-Ergebnis speichern echo "{\"service\":\"backend\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" \ > ../.ci-results/results-backend.json ``` ### Report-Step ```yaml report-test-results: image: curlimages/curl:8.10.1 commands: - | set -uo pipefail echo "=== Sende Test-Ergebnisse an Dashboard ===" echo "Pipeline Status: ${CI_PIPELINE_STATUS:-unknown}" ls -la .ci-results/ || echo "Verzeichnis nicht gefunden" PIPELINE_STATUS="${CI_PIPELINE_STATUS:-unknown}" # Schleife über alle Ergebnis-Dateien for f in .ci-results/results-*.json; do [ -f "$f" ] || continue echo "Sending: $f" curl -f -sS -X POST "http://backend:8000/api/tests/ci-result" \ -H "Content-Type: application/json" \ -d "{ \"pipeline_id\": \"${CI_PIPELINE_NUMBER}\", \"commit\": \"${CI_COMMIT_SHA}\", \"branch\": \"${CI_COMMIT_BRANCH}\", \"status\": \"${PIPELINE_STATUS}\", \"test_results\": $(cat "$f") }" || echo "WARNUNG: Konnte $f nicht senden" done echo "=== Test-Ergebnisse gesendet ===" when: status: [success, failure] # Läuft immer, auch bei Test-Fehlern depends_on: - test-go-consent - test-go-billing - test-go-school - test-python-backend - test-python-voice - test-python-klausur - test-nodejs-h5p ``` **Wichtige Verbesserungen:** - **Vollständiges `depends_on`:** Alle Test-Steps inklusive integration-tests sind aufgelistet - **Schleife statt if-Blöcke:** Reduziert Code-Duplikation - **Dynamischer Status:** `${CI_PIPELINE_STATUS}` statt hardcodiertem `"success"` - **curl mit `-f` Flag:** Zeigt HTTP-Fehler an - **Pinned Image-Version:** `curl:8.10.1` statt `latest` --- ### 2.5 Integration Tests Nach den Unit-Tests laeuft ein vollstaendiger Integration-Test-Schritt, der alle Services in einer Docker-Compose-Umgebung testet. #### Docker Compose Services | Service | Container-Name | Port (extern) | Port (intern) | |---------|----------------|---------------|---------------| | PostgreSQL | postgres-test | 55432 | 5432 | | Valkey | valkey-test | 56379 | 6379 | | Consent Service | consent-service-test | 58081 | 8081 | | Backend | backend-test | 58000 | 8000 | | Mailpit | mailpit-test | 58025/51025 | 8025/1025 | #### Pipeline-Step Konfiguration ```yaml integration-tests: image: docker:27-cli volumes: - /var/run/docker.sock:/var/run/docker.sock commands: - | # 1. Docker Compose Umgebung starten docker compose -f docker-compose.test.yml up -d # 2. Auf healthy Services warten (Timeout: 120s pro Service) for service in postgres-test valkey-test consent-service-test backend-test; do echo "Waiting for $service..." timeout=120 while [ $elapsed -lt $timeout ]; do status=$(docker compose -f docker-compose.test.yml ps $service --format json | jq -r '.[0].Health') if [ "$status" = "healthy" ]; then break; fi sleep 5 done done # 3. Integration Tests im Backend-Container ausfuehren docker compose -f docker-compose.test.yml exec -T backend-test \ pytest tests/test_integration/ -v --tb=short \ --json-report --json-report-file=/tmp/integration-results.json # 4. Cleanup docker compose -f docker-compose.test.yml down -v when: - event: [push, pull_request] branch: [main, develop] depends_on: - test-python-backend ``` #### Environment Variables im Integration-Modus | Variable | Wert | |----------|------| | `SKIP_INTEGRATION_TESTS` | `false` | | `DATABASE_URL` | `postgresql://breakpilot:breakpilot_test@postgres-test:5432/breakpilot_test` | | `CONSENT_SERVICE_URL` | `http://consent-service-test:8081` | | `VALKEY_URL` / `REDIS_URL` | `redis://valkey-test:6379` | | `SMTP_HOST` / `SMTP_PORT` | `mailpit-test` / `1025` | #### Lokales Testen der Integration-Tests ```bash # 1. Test-Umgebung starten docker compose -f docker-compose.test.yml up -d # 2. Warten bis healthy docker compose -f docker-compose.test.yml ps # 3. Tests ausfuehren cd backend export SKIP_INTEGRATION_TESTS=false pytest tests/test_integration/ -v # 4. Aufraeumen docker compose -f docker-compose.test.yml down -v ``` #### Troubleshooting Integration Tests | Problem | Loesung | |---------|---------| | Service nicht healthy | `docker compose -f docker-compose.test.yml logs --tail=100` | | Port bereits belegt | `lsof -i :` und bestehende Container stoppen | | Tests finden keine Services | Sicherstellen dass `SKIP_INTEGRATION_TESTS=false` gesetzt ist | | Timeout beim Warten | Health-Check-Intervalle in docker-compose.test.yml anpassen | **Weitere Details:** Siehe [Integration Test Environment Dokumentation](../testing/integration-test-environment.md) --- ### CI-Result JSON-Format ```json { "pipeline_id": "27", "commit": "abc12345", "branch": "main", "status": "success", "test_results": { "service": "consent-service", "framework": "go", "total": 57, "passed": 57, "failed": 0, "skipped": 0, "coverage": 75.5 } } ``` --- ## 3. Test Registry Backend ### Dateien | Datei | Beschreibung | |-------|--------------| | `backend/api/tests/registry.py` | Haupt-API Router (~2200 Zeilen) | | `backend/api/tests/models.py` | Pydantic/Dataclass Models | | `backend/api/tests/db_models.py` | SQLAlchemy DB Models | | `backend/api/tests/repository.py` | Datenbank-Repository | | `backend/api/tests/database.py` | DB-Session Management | ### Datenfluss bei CI-Result ```python @router.post("/ci-result") async def receive_ci_result(result: CIResultRequest, background_tasks: BackgroundTasks): """ 1. Extrahiere Service-Daten aus test_results 2. Erstelle TestRunDB Eintrag 3. Aktualisiere TestServiceStatsDB 4. Aktualisiere In-Memory Cache (_persisted_results) 5. Bei Fehlern: Erstelle FailedTestBacklogDB Eintrag (Background Task) 6. Bei 0 Fehlern: Schließe offene Backlog-Einträge (Background Task) """ ``` ### In-Memory Cache ```python # Wird beim Start aus PostgreSQL geladen _persisted_results: Dict[str, Dict] = {} # Struktur pro Service: { "consent-service": { "total": 57, "passed": 57, "failed": 0, "last_run": "2026-02-02T18:46:50", "status": "passed", "failed_test_ids": [] } } # Wird bei jedem CI-Result sofort aktualisiert für Echtzeit-Updates ``` --- ## 4. Datenbank-Schema ### TestRunDB (test_runs) Speichert jeden Test-Durchlauf. ```sql CREATE TABLE test_runs ( id SERIAL PRIMARY KEY, run_id VARCHAR(50) UNIQUE NOT NULL, -- z.B. "ci-27-consent-service" service VARCHAR(100) NOT NULL, framework VARCHAR(50) NOT NULL, -- go, pytest, jest started_at TIMESTAMP NOT NULL, completed_at TIMESTAMP, status VARCHAR(20) NOT NULL, -- queued, running, completed, failed total_tests INTEGER DEFAULT 0, passed_tests INTEGER DEFAULT 0, failed_tests INTEGER DEFAULT 0, skipped_tests INTEGER DEFAULT 0, duration_seconds FLOAT DEFAULT 0, git_commit VARCHAR(40), git_branch VARCHAR(100), triggered_by VARCHAR(50), -- manual, ci, schedule output TEXT, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_test_runs_service ON test_runs(service); CREATE INDEX idx_test_runs_started_at ON test_runs(started_at); ``` ### TestResultDB (test_results) Speichert einzelne Test-Ergebnisse. ```sql CREATE TABLE test_results ( id SERIAL PRIMARY KEY, run_id VARCHAR(50) REFERENCES test_runs(run_id) ON DELETE CASCADE, test_name VARCHAR(500) NOT NULL, test_file VARCHAR(500), line_number INTEGER, status VARCHAR(20) NOT NULL, -- passed, failed, skipped, error duration_ms FLOAT, error_message TEXT, error_type VARCHAR(100), output TEXT, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_test_results_run_id ON test_results(run_id); CREATE INDEX idx_test_results_status ON test_results(status); ``` ### FailedTestBacklogDB (failed_tests_backlog) Persistenter Backlog für fehlgeschlagene Tests. ```sql CREATE TABLE failed_tests_backlog ( id SERIAL PRIMARY KEY, test_name VARCHAR(500) NOT NULL, test_file VARCHAR(500), service VARCHAR(100) NOT NULL, framework VARCHAR(50), error_message TEXT, error_type VARCHAR(100), first_failed_at TIMESTAMP NOT NULL, last_failed_at TIMESTAMP NOT NULL, failure_count INTEGER DEFAULT 1, status VARCHAR(30) DEFAULT 'open', -- open, in_progress, fixed, wont_fix, flaky priority VARCHAR(20) DEFAULT 'medium', -- critical, high, medium, low assigned_to VARCHAR(100), fix_suggestion TEXT, notes TEXT, -- Auto-Close Felder resolved_at TIMESTAMP, resolution_commit VARCHAR(50), resolution_notes TEXT, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), CONSTRAINT uq_backlog_test_service UNIQUE (test_name, service) ); CREATE INDEX idx_backlog_service ON failed_tests_backlog(service); CREATE INDEX idx_backlog_status ON failed_tests_backlog(status); CREATE INDEX idx_backlog_priority ON failed_tests_backlog(priority); ``` ### TestServiceStatsDB (test_service_stats) Aggregierte Statistiken pro Service für schnelle Abfragen. ```sql CREATE TABLE test_service_stats ( id SERIAL PRIMARY KEY, service VARCHAR(100) UNIQUE NOT NULL, total_tests INTEGER DEFAULT 0, passed_tests INTEGER DEFAULT 0, failed_tests INTEGER DEFAULT 0, skipped_tests INTEGER DEFAULT 0, pass_rate FLOAT DEFAULT 0.0, last_run_id VARCHAR(50), last_run_at TIMESTAMP, last_status VARCHAR(20), updated_at TIMESTAMP DEFAULT NOW() ); ``` ### TestFixHistoryDB (test_fixes_history) Historie aller Fix-Versuche. ```sql CREATE TABLE test_fixes_history ( id SERIAL PRIMARY KEY, backlog_id INTEGER REFERENCES failed_tests_backlog(id) ON DELETE CASCADE, fix_type VARCHAR(50), -- manual, auto_claude, auto_script fix_description TEXT, commit_hash VARCHAR(40), success BOOLEAN, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_fixes_backlog_id ON test_fixes_history(backlog_id); ``` --- ## 5. Backlog-System ### Status-Workflow ``` ┌─────────────┐ │ │ Test schlägt ──▶│ open │◀── Test schlägt erneut fehl fehl │ │ └──────┬──────┘ │ │ Entwickler beginnt Fix ▼ ┌─────────────┐ │ │ │ in_progress │ │ │ └──────┬──────┘ │ ┌───────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ │ fixed │ │ wont_fix │ │ flaky │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ ▲ │ │ Automatisch wenn alle │ Tests bestehen ┌──────┴──────┐ │ resolved │ │ (auto-close)│ └─────────────┘ ``` ### Automatische Backlog-Erstellung Bei `failed > 0` in CI-Result: ```python async def _create_backlog_entry(service_name, framework, failed_count, pipeline_id, commit, branch): """ 1. Prüfe ob Eintrag für Service bereits existiert 2. Falls ja: Erhöhe failure_count, aktualisiere last_failed_at 3. Falls nein: Erstelle neuen Eintrag mit status='open' """ with get_db_session() as db: existing = db.query(FailedTestBacklogDB).filter( FailedTestBacklogDB.service == service_name, FailedTestBacklogDB.status == "open" ).first() if existing: existing.failure_count += failed_count existing.last_failed_at = datetime.utcnow() else: entry = FailedTestBacklogDB( test_name=f"Pipeline {pipeline_id} - {failed_count} Tests", service=service_name, framework=framework, status="open", priority="medium", first_failed_at=datetime.utcnow(), last_failed_at=datetime.utcnow(), failure_count=failed_count, fix_suggestion="Analysiere den Stack-Trace für Details." ) db.add(entry) db.commit() ``` ### Automatisches Schließen (Auto-Close) Bei `failed == 0` in CI-Result: ```python async def _close_backlog_entry(service_name, pipeline_id, commit): """ Schließt alle offenen Backlog-Einträge für einen Service, wenn alle Tests bestanden haben. """ with get_db_session() as db: open_entries = db.query(FailedTestBacklogDB).filter( FailedTestBacklogDB.service == service_name, FailedTestBacklogDB.status == "open" ).all() for entry in open_entries: entry.status = "resolved" entry.resolved_at = datetime.utcnow() entry.resolution_commit = commit[:8] if commit else None entry.resolution_notes = f"Automatisch geschlossen - alle Tests in Pipeline {pipeline_id} bestanden" db.commit() ``` ### Prioritäts-Regeln | Priorität | Kriterium | |-----------|-----------| | `critical` | > 10 Fehler oder Service-kritisch | | `high` | 5-10 Fehler oder häufige Regression | | `medium` | 1-4 Fehler (Standard) | | `low` | Flaky Tests oder Edge Cases | --- ## 6. Frontend Dashboard ### URLs | Dashboard | URL | Beschreibung | |-----------|-----|--------------| | Test Dashboard | `https://macmini:3002/infrastructure/tests` | Übersicht aller Services | | CI/CD Dashboard | `https://macmini:3002/infrastructure/ci-cd` | Pipeline-Status | | Backlog | `https://macmini:3002/infrastructure/tests` (Tab) | Fehlgeschlagene Tests | ### Komponenten **Pfad:** `admin-v2/app/(admin)/infrastructure/tests/page.tsx` ```typescript // Haupt-Komponenten // Kacheln für jeden Service // Letzte Test-Durchläufe // Offene Backlog-Einträge // Coverage-Visualisierung ``` ### API-Aufrufe ```typescript // Test Registry laden const registry = await fetch('/api/tests/registry').then(r => r.json()); // Backlog laden const backlog = await fetch('/api/tests/backlog').then(r => r.json()); // Test-Runs laden const runs = await fetch('/api/tests/runs').then(r => r.json()); // Coverage laden const coverage = await fetch('/api/tests/coverage').then(r => r.json()); ``` --- ## 7. Service-Übersicht ### Registrierte Services | Service | Sprache | Framework | Port | Tests | Status | |---------|---------|-----------|------|-------|--------| | consent-service | Go | go_test | 8081 | ~60 | Aktiv | | billing-service | Go | go_test | 8082 | ~20 | Aktiv | | school-service | Go | go_test | 8084 | ~15 | Aktiv | | backend | Python | pytest | 8000 | ~200 | Aktiv | | voice-service | Python | pytest | 8091 | ~30 | Aktiv (inkl. BQAS) | | klausur-service | Python | pytest | 8086 | ~150 | Aktiv | | h5p-service | Node.js | jest | - | ~25 | Aktiv | | edu-search-service | Go | go_test | 8088 | 0 | Keine Tests | | ai-compliance-sdk | Go | go_test | - | ~150 | Nicht in Pipeline | | website | TypeScript | jest | 3000 | ~0 | Nicht in Pipeline | | bqas-golden | Python | pytest | 8091 | ~10 | In voice-service | | bqas-rag | Python | pytest | 8091 | ~10 | In voice-service | ### Service-Definition (models.py) ```python SERVICE_DEFINITIONS = [ { "service": "consent-service", "display_name": "Consent Service", "port": 8081, "language": "go", "base_path": "/consent-service", "test_pattern": "*_test.go", "framework": TestFramework.GO_TEST, }, # ... weitere Services ] ``` ### Tests pro Service #### consent-service (Go) ``` consent-service/ ├── internal/ │ ├── handlers/ │ │ └── handlers_test.go # HTTP Handler Tests │ ├── services/ │ │ ├── auth_service_test.go # Auth Tests │ │ └── consent_service_test.go │ └── middleware/ │ └── middleware_test.go └── cmd/ └── server_test.go ``` #### backend (Python) ``` backend/tests/ ├── test_consent_client.py ├── test_gdpr_api.py ├── test_documents.py ├── test_worksheets_api.py ├── test_auth.py └── ... ``` #### voice-service (Python) ``` voice-service/tests/ ├── test_encryption.py ├── test_intent_router.py ├── test_sessions.py ├── test_tasks.py └── bqas/ ├── test_golden.py # BQAS Golden Suite └── test_rag.py # BQAS RAG Tests ``` #### klausur-service (Python) ``` klausur-service/backend/tests/ ├── test_advanced_rag.py ├── test_byoeh.py ├── test_mail_service.py ├── test_ocr_labeling.py ├── test_rag_admin.py ├── test_rbac.py ├── test_vocab_worksheet.py └── test_worksheet_editor.py ``` #### h5p-service (Node.js) ``` h5p-service/tests/ ├── server.test.js ├── setup.js └── README.md ``` --- ## 8. Fehlerbehebung ### Problem: Tests zeigen 0/0 an **Ursache:** Pipeline-Tests produzieren keine Ergebnisse **Lösung:** 1. Prüfe Woodpecker Agent Logs: ```bash docker logs breakpilot-pwa-woodpecker-agent --tail=100 ``` 2. Prüfe ob Service kompiliert: ```bash cd billing-service && go build ./... ``` 3. Prüfe ob Tests lokal laufen: ```bash cd billing-service && go test -v ./... ``` ### Problem: Backlog zeigt 500 Error **Ursache:** Fehlende DB-Spalten **Lösung:** ```sql ALTER TABLE failed_tests_backlog ADD COLUMN resolved_at TIMESTAMP; ALTER TABLE failed_tests_backlog ADD COLUMN resolution_commit VARCHAR(50); ALTER TABLE failed_tests_backlog ADD COLUMN resolution_notes TEXT; ``` ### Problem: Frontend zeigt alte Daten **Ursache:** In-Memory Cache nicht aktualisiert **Lösung:** 1. Backend neustarten: ```bash docker compose restart backend ``` 2. Oder: Manuell Cache-Refresh via API (wenn implementiert) ### Problem: Pipeline startet nicht **Ursache:** Webhook von Gitea nicht empfangen **Lösung:** 1. Prüfe Gitea Webhook-Konfiguration 2. Prüfe Woodpecker Server Logs: ```bash docker logs breakpilot-pwa-woodpecker-server --tail=50 ``` 3. Manuell Pipeline triggern via Woodpecker UI ### Problem: OOM Kill in Pipeline **Ursache:** Zu viele parallele Tests **Lösung:** 1. Tests sequentiell statt parallel ausführen 2. Memory-Limits in docker-compose erhöhen 3. Große Test-Suites aufteilen --- ## 9. API-Referenz ### Test Registry Endpoints #### GET /api/tests/registry Gibt alle registrierten Services mit Test-Statistiken zurück. **Response:** ```json { "services": [ { "service": "consent-service", "display_name": "Consent Service", "port": 8081, "language": "go", "total_tests": 57, "passed_tests": 57, "failed_tests": 0, "skipped_tests": 0, "pass_rate": 100.0, "coverage_percent": 75.5, "last_run": "2026-02-02T18:46:50", "status": "passed" } ], "stats": { "total_tests": 500, "total_passed": 480, "total_failed": 20, "services_count": 12, "overall_pass_rate": 96.0 } } ``` #### POST /api/tests/ci-result Empfängt Test-Ergebnisse von der CI/CD-Pipeline. **Request:** ```json { "pipeline_id": "27", "commit": "abc12345def67890", "branch": "main", "status": "success", "test_results": { "service": "consent-service", "framework": "go", "total": 57, "passed": 57, "failed": 0, "skipped": 0, "coverage": 75.5 } } ``` **Response:** ```json { "received": true, "run_id": "ci-27-consent-service", "service": "consent-service", "pipeline_id": "27", "status": "passed", "tests": {"total": 57, "passed": 57, "failed": 0}, "stored_in": "postgres" } ``` #### GET /api/tests/backlog Gibt alle Backlog-Einträge zurück. **Query-Parameter:** - `status`: Filter nach Status (open, in_progress, fixed, etc.) - `service`: Filter nach Service - `limit`: Anzahl der Ergebnisse (default: 50) **Response:** ```json { "total": 15, "items": [ { "id": 1, "test_name": "Pipeline 27 - 3 Tests", "service": "backend", "status": "open", "priority": "medium", "failure_count": 3, "first_failed_at": "2026-02-02T10:00:00", "last_failed_at": "2026-02-02T18:00:00", "resolved_at": null } ] } ``` #### GET /api/tests/runs Gibt die letzten Test-Durchläufe zurück. **Query-Parameter:** - `service`: Filter nach Service - `limit`: Anzahl (default: 20) #### GET /api/tests/coverage Gibt Coverage-Informationen zurück. #### POST /api/tests/run/{suite} Startet einen Test-Run manuell. #### PUT /api/tests/backlog/{id} Aktualisiert einen Backlog-Eintrag (Status, Priorität, Assignee). --- ## Anhang ### Umgebungsvariablen ```bash # Woodpecker WOODPECKER_URL=https://macmini:4431 WOODPECKER_TOKEN= # Backend DATABASE_URL=postgresql://user:pass@postgres:5432/breakpilot TEST_REGISTRY_ENABLED=true ``` ### Lokale CI-Simulation Für lokales Testen ohne Woodpecker CI stehen zwei Hilfsdateien zur Verfügung: **Makefile** (Projektroot) ```bash # Alle Tests lokal ausführen make ci # Nur Go-Tests make test-go # Nur Python-Tests make test-python # Woodpecker Agent Logs make logs-agent # Backend Logs (ci-result Filter) make logs-backend # Test-Ergebnisse löschen make clean ``` **docker-compose.test.yml** (Projektroot) ```bash # Test-Datenbanken starten docker compose -f docker-compose.test.yml up -d # Test-Datenbanken stoppen und Daten löschen docker compose -f docker-compose.test.yml down -v ``` | Service | Port | Credentials | |---------|------|-------------| | PostgreSQL | 55432 | breakpilot_test/breakpilot/breakpilot | | Redis | 56379 | (keine) | --- ### Nützliche Befehle ```bash # Woodpecker Logs docker logs breakpilot-pwa-woodpecker-agent --tail=100 docker logs breakpilot-pwa-woodpecker-server --tail=100 # Backend Logs docker compose logs backend --tail=100 | grep -E "(CI-RESULT|test|error)" # Datenbank prüfen docker compose exec backend python3 -c " from sqlalchemy.orm import Session from classroom_engine.database import engine from api.tests.db_models import TestRunDB, TestServiceStatsDB with Session(engine) as db: runs = db.query(TestRunDB).order_by(TestRunDB.started_at.desc()).limit(5).all() for r in runs: print(f'{r.run_id}: {r.status} - {r.passed_tests}/{r.total_tests}') " # Pipeline manuell triggern curl -X POST "https://macmini:4431/api/repos/pilotadmin/breakpilot-pwa/pipelines" \ -H "Authorization: Bearer $WOODPECKER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"branch":"main"}' ``` ### Weiterführende Dokumentation - [Woodpecker CI Docs](https://woodpecker-ci.org/docs/intro) - [BQAS Quality System](../architecture/bqas-quality-system.md) - [Voice Service Guide](../guides/voice-service-developer-guide.md) - [E2E Testing Guide](../guides/E2E-Testing-Playwright.md) --- *Generiert am 2026-02-02 von Claude Code* *Aktualisiert am 2026-02-02: jq-Parsing für Go-Tests, vollständiges depends_on, Makefile & docker-compose.test.yml* *Aktualisiert am 2026-02-04: Integration Test Environment mit Docker Compose hinzugefuegt (Sektion 2.5)*