# Woodpecker CI Main Pipeline # BreakPilot PWA - CI/CD Pipeline # # Plattform: ARM64 (Apple Silicon Mac Mini) # # Strategie: # - Tests laufen bei JEDEM Push/PR # - Test-Ergebnisse werden an Dashboard gesendet # - Builds/Scans laufen nur bei Tags oder manuell # - Deployment nur manuell (Sicherheit) when: - event: [push, pull_request, manual, tag] branch: [main, develop] clone: git: image: woodpeckerci/plugin-git settings: depth: 1 extra_hosts: - macmini:192.168.178.100 variables: - &golang_image golang:1.23-alpine - &python_image python:3.12-slim - &python_ci_image breakpilot/python-ci:3.12 # Custom image with WeasyPrint - &nodejs_image node:20-alpine - &docker_image docker:27-cli steps: # ======================================== # STAGE 1: Lint (nur bei PRs) # ======================================== go-lint: image: golangci/golangci-lint:v1.55-alpine commands: - cd consent-service && golangci-lint run --timeout 5m ./... - cd ../billing-service && golangci-lint run --timeout 5m ./... - cd ../school-service && golangci-lint run --timeout 5m ./... when: event: pull_request python-lint: image: *python_image commands: - pip install --quiet ruff black - ruff check backend/ --output-format=github || true - black --check backend/ || true when: event: pull_request # ======================================== # STAGE 2: Unit Tests mit JSON-Ausgabe # Ergebnisse werden im Workspace gespeichert (.ci-results/) # ======================================== test-go-consent: image: *golang_image environment: CGO_ENABLED: "0" commands: - | set -euo pipefail apk add --no-cache jq bash mkdir -p .ci-results 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 # JSON-Zeilen extrahieren und mit jq zählen JSON_FILE="../.ci-results/test-consent.json" if grep -q '^{' "$JSON_FILE" 2>/dev/null; then TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length') PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length') FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length') SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length') else echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)" TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi 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 # Backlog-Strategie: Fehler werden gemeldet aber Pipeline laeuft weiter if [ "$FAILED" -gt "0" ]; then echo "WARNUNG: $FAILED Tests fehlgeschlagen - werden ins Backlog geschrieben" fi test-go-billing: image: *golang_image environment: CGO_ENABLED: "0" commands: - | set -euo pipefail apk add --no-cache jq bash mkdir -p .ci-results if [ ! -d "billing-service" ]; then echo '{"service":"billing-service","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-billing.json echo "WARNUNG: billing-service Verzeichnis nicht gefunden" exit 0 fi cd billing-service set +e go test -v -json -coverprofile=coverage.out ./... 2>&1 | tee ../.ci-results/test-billing.json TEST_EXIT=$? set -e # JSON-Zeilen extrahieren und mit jq zählen JSON_FILE="../.ci-results/test-billing.json" if grep -q '^{' "$JSON_FILE" 2>/dev/null; then TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length') PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length') FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length') SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length') else echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)" TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi 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\":\"billing-service\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":$COVERAGE}" > ../.ci-results/results-billing.json cat ../.ci-results/results-billing.json # Backlog-Strategie: Fehler werden gemeldet aber Pipeline laeuft weiter if [ "$FAILED" -gt "0" ]; then echo "WARNUNG: $FAILED Tests fehlgeschlagen - werden ins Backlog geschrieben" fi test-go-school: image: *golang_image environment: CGO_ENABLED: "0" commands: - | set -euo pipefail apk add --no-cache jq bash mkdir -p .ci-results if [ ! -d "school-service" ]; then echo '{"service":"school-service","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-school.json echo "WARNUNG: school-service Verzeichnis nicht gefunden" exit 0 fi cd school-service set +e go test -v -json -coverprofile=coverage.out ./... 2>&1 | tee ../.ci-results/test-school.json TEST_EXIT=$? set -e # JSON-Zeilen extrahieren und mit jq zählen JSON_FILE="../.ci-results/test-school.json" if grep -q '^{' "$JSON_FILE" 2>/dev/null; then TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length') PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length') FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length') SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length') else echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)" TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi 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\":\"school-service\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":$COVERAGE}" > ../.ci-results/results-school.json cat ../.ci-results/results-school.json # Backlog-Strategie: Fehler werden gemeldet aber Pipeline laeuft weiter if [ "$FAILED" -gt "0" ]; then echo "WARNUNG: $FAILED Tests fehlgeschlagen - werden ins Backlog geschrieben" fi test-go-edu-search: image: *golang_image environment: CGO_ENABLED: "0" commands: - | set -euo pipefail apk add --no-cache jq bash mkdir -p .ci-results if [ ! -d "edu-search-service" ]; then echo '{"service":"edu-search-service","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-edu-search.json echo "WARNUNG: edu-search-service Verzeichnis nicht gefunden" exit 0 fi cd edu-search-service set +e go test -v -json -coverprofile=coverage.out ./internal/... 2>&1 | tee ../.ci-results/test-edu-search.json TEST_EXIT=$? set -e # JSON-Zeilen extrahieren und mit jq zählen JSON_FILE="../.ci-results/test-edu-search.json" if grep -q '^{' "$JSON_FILE" 2>/dev/null; then TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length') PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length') FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length') SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length') else echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)" TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi 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\":\"edu-search-service\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":$COVERAGE}" > ../.ci-results/results-edu-search.json cat ../.ci-results/results-edu-search.json # Backlog-Strategie: Fehler werden gemeldet aber Pipeline laeuft weiter if [ "$FAILED" -gt "0" ]; then echo "WARNUNG: $FAILED Tests fehlgeschlagen - werden ins Backlog geschrieben" fi test-go-ai-compliance: image: *golang_image environment: CGO_ENABLED: "0" commands: - | set -euo pipefail apk add --no-cache jq bash mkdir -p .ci-results if [ ! -d "ai-compliance-sdk" ]; then echo '{"service":"ai-compliance-sdk","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-ai-compliance.json echo "WARNUNG: ai-compliance-sdk Verzeichnis nicht gefunden" exit 0 fi cd ai-compliance-sdk set +e go test -v -json -coverprofile=coverage.out ./... 2>&1 | tee ../.ci-results/test-ai-compliance.json TEST_EXIT=$? set -e # JSON-Zeilen extrahieren und mit jq zählen JSON_FILE="../.ci-results/test-ai-compliance.json" if grep -q '^{' "$JSON_FILE" 2>/dev/null; then TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length') PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length') FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length') SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length') else echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)" TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi 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\":\"ai-compliance-sdk\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":$COVERAGE}" > ../.ci-results/results-ai-compliance.json cat ../.ci-results/results-ai-compliance.json # Backlog-Strategie: Fehler werden gemeldet aber Pipeline laeuft weiter if [ "$FAILED" -gt "0" ]; then echo "WARNUNG: $FAILED Tests fehlgeschlagen - werden ins Backlog geschrieben" fi test-python-backend: image: *python_ci_image environment: CI: "true" DATABASE_URL: "postgresql://test:test@localhost:5432/test_db" SKIP_DB_TESTS: "true" SKIP_WEASYPRINT_TESTS: "false" SKIP_INTEGRATION_TESTS: "true" commands: - | set -uo pipefail mkdir -p .ci-results if [ ! -d "backend" ]; then echo '{"service":"backend","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-backend.json echo "WARNUNG: backend Verzeichnis nicht gefunden" exit 0 fi cd backend # Set PYTHONPATH to current directory (backend) so local packages like classroom_engine, alerts_agent are found # IMPORTANT: Use absolute path and export before pip install to ensure modules are available export PYTHONPATH="$(pwd):${PYTHONPATH:-}" # Test tools are pre-installed in breakpilot/python-ci image # Only install project-specific dependencies pip install --quiet --no-cache-dir -r requirements.txt # NOTE: PostgreSQL service removed - tests that require DB are skipped via SKIP_DB_TESTS=true # For full integration tests, use: docker compose -f docker-compose.test.yml up -d set +e # Use python -m pytest to ensure PYTHONPATH is properly applied before pytest starts python -m pytest tests/ -v --tb=short --cov=. --cov-report=term-missing --json-report --json-report-file=../.ci-results/test-backend.json TEST_EXIT=$? set -e 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))" 2>/dev/null || echo "0") PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-backend.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-backend.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-backend.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") else TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi echo "{\"service\":\"backend\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-backend.json cat ../.ci-results/results-backend.json if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi test-python-voice: image: *python_image environment: CI: "true" commands: - | set -uo pipefail mkdir -p .ci-results if [ ! -d "voice-service" ]; then echo '{"service":"voice-service","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-voice.json echo "WARNUNG: voice-service Verzeichnis nicht gefunden" exit 0 fi cd voice-service export PYTHONPATH="$(pwd):${PYTHONPATH:-}" pip install --quiet --no-cache-dir -r requirements.txt pip install --quiet --no-cache-dir pytest-json-report set +e python -m pytest tests/ -v --tb=short --json-report --json-report-file=../.ci-results/test-voice.json TEST_EXIT=$? set -e if [ -f ../.ci-results/test-voice.json ]; then TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0") PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") else TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi echo "{\"service\":\"voice-service\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-voice.json cat ../.ci-results/results-voice.json if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi test-bqas-golden: image: *python_image commands: - | set -uo pipefail mkdir -p .ci-results if [ ! -d "voice-service/tests/bqas" ]; then echo '{"service":"bqas-golden","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-bqas-golden.json echo "WARNUNG: voice-service/tests/bqas Verzeichnis nicht gefunden" exit 0 fi cd voice-service export PYTHONPATH="$(pwd):${PYTHONPATH:-}" pip install --quiet --no-cache-dir -r requirements.txt pip install --quiet --no-cache-dir pytest-json-report pytest-asyncio set +e python -m pytest tests/bqas/test_golden.py tests/bqas/test_regression.py tests/bqas/test_synthetic.py -v --tb=short --json-report --json-report-file=../.ci-results/test-bqas-golden.json TEST_EXIT=$? set -e if [ -f ../.ci-results/test-bqas-golden.json ]; then TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0") PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") else TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi echo "{\"service\":\"bqas-golden\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-bqas-golden.json cat ../.ci-results/results-bqas-golden.json # BQAS tests may skip if Ollama not available - don't fail pipeline if [ "$FAILED" -gt "0" ]; then exit 1; fi test-bqas-rag: image: *python_image commands: - | set -uo pipefail mkdir -p .ci-results if [ ! -d "voice-service/tests/bqas" ]; then echo '{"service":"bqas-rag","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-bqas-rag.json echo "WARNUNG: voice-service/tests/bqas Verzeichnis nicht gefunden" exit 0 fi cd voice-service export PYTHONPATH="$(pwd):${PYTHONPATH:-}" pip install --quiet --no-cache-dir -r requirements.txt pip install --quiet --no-cache-dir pytest-json-report pytest-asyncio set +e python -m pytest tests/bqas/test_rag.py tests/bqas/test_notifier.py -v --tb=short --json-report --json-report-file=../.ci-results/test-bqas-rag.json TEST_EXIT=$? set -e if [ -f ../.ci-results/test-bqas-rag.json ]; then TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0") PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") else TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi echo "{\"service\":\"bqas-rag\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-bqas-rag.json cat ../.ci-results/results-bqas-rag.json # BQAS tests may skip if Ollama not available - don't fail pipeline if [ "$FAILED" -gt "0" ]; then exit 1; fi test-python-klausur: image: *python_image environment: CI: "true" commands: - | set -uo pipefail mkdir -p .ci-results if [ ! -d "klausur-service/backend" ]; then echo '{"service":"klausur-service","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-klausur.json echo "WARNUNG: klausur-service/backend Verzeichnis nicht gefunden" exit 0 fi cd klausur-service/backend # Set PYTHONPATH to current directory so local modules like hyde, hybrid_search, etc. are found export PYTHONPATH="$(pwd):${PYTHONPATH:-}" pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || pip install --quiet --no-cache-dir fastapi uvicorn pytest pytest-asyncio pytest-json-report pip install --quiet --no-cache-dir pytest-json-report set +e python -m pytest tests/ -v --tb=short --json-report --json-report-file=../../.ci-results/test-klausur.json TEST_EXIT=$? set -e if [ -f ../../.ci-results/test-klausur.json ]; then TOTAL=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0") PASSED=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") FAILED=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") SKIPPED=$(python3 -c "import json; d=json.load(open('../../.ci-results/test-klausur.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") else TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi echo "{\"service\":\"klausur-service\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../../.ci-results/results-klausur.json cat ../../.ci-results/results-klausur.json if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi test-nodejs-h5p: image: *nodejs_image commands: - | set -uo pipefail mkdir -p .ci-results if [ ! -d "h5p-service" ]; then echo '{"service":"h5p-service","framework":"jest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-h5p.json echo "WARNUNG: h5p-service Verzeichnis nicht gefunden" exit 0 fi cd h5p-service npm ci --silent 2>/dev/null || npm install --silent set +e npm run test:ci -- --json --outputFile=../.ci-results/test-h5p.json 2>&1 TEST_EXIT=$? set -e if [ -f ../.ci-results/test-h5p.json ]; then TOTAL=$(node -e "const d=require('../.ci-results/test-h5p.json'); console.log(d.numTotalTests || 0)" 2>/dev/null || echo "0") PASSED=$(node -e "const d=require('../.ci-results/test-h5p.json'); console.log(d.numPassedTests || 0)" 2>/dev/null || echo "0") FAILED=$(node -e "const d=require('../.ci-results/test-h5p.json'); console.log(d.numFailedTests || 0)" 2>/dev/null || echo "0") SKIPPED=$(node -e "const d=require('../.ci-results/test-h5p.json'); console.log(d.numPendingTests || 0)" 2>/dev/null || echo "0") else TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 fi [ -z "$TOTAL" ] && TOTAL=0 [ -z "$PASSED" ] && PASSED=0 [ -z "$FAILED" ] && FAILED=0 [ -z "$SKIPPED" ] && SKIPPED=0 echo "{\"service\":\"h5p-service\",\"framework\":\"jest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-h5p.json cat ../.ci-results/results-h5p.json if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi # ======================================== # STAGE 2.5: Integration Tests # ======================================== # Integration Tests laufen in separater Pipeline: # .woodpecker/integration.yml # (benötigt Pipeline-Level Services für PostgreSQL und Valkey) # ======================================== # STAGE 3: Test-Ergebnisse an Dashboard senden # ======================================== 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}" 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] depends_on: - test-go-consent - test-go-billing - test-go-school - test-go-edu-search - test-go-ai-compliance - test-python-backend - test-python-voice - test-bqas-golden - test-bqas-rag - test-python-klausur - test-nodejs-h5p # ======================================== # STAGE 4: Build & Security (nur Tags/manuell) # ======================================== build-consent-service: image: *docker_image commands: - docker build -t breakpilot/consent-service:${CI_COMMIT_SHA:0:8} ./consent-service - docker tag breakpilot/consent-service:${CI_COMMIT_SHA:0:8} breakpilot/consent-service:latest - echo "Built breakpilot/consent-service:${CI_COMMIT_SHA:0:8}" when: - event: tag - event: manual build-backend: image: *docker_image commands: - docker build -t breakpilot/backend:${CI_COMMIT_SHA:0:8} ./backend - docker tag breakpilot/backend:${CI_COMMIT_SHA:0:8} breakpilot/backend:latest - echo "Built breakpilot/backend:${CI_COMMIT_SHA:0:8}" when: - event: tag - event: manual build-voice-service: image: *docker_image commands: - | if [ -d ./voice-service ]; then docker build -t breakpilot/voice-service:${CI_COMMIT_SHA:0:8} ./voice-service docker tag breakpilot/voice-service:${CI_COMMIT_SHA:0:8} breakpilot/voice-service:latest echo "Built breakpilot/voice-service:${CI_COMMIT_SHA:0:8}" else echo "voice-service Verzeichnis nicht gefunden - ueberspringe" fi when: - event: tag - event: manual generate-sbom: image: *golang_image commands: - | echo "Installing syft for ARM64..." wget -qO- https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin syft dir:./consent-service -o cyclonedx-json > sbom-consent.json syft dir:./backend -o cyclonedx-json > sbom-backend.json if [ -d ./voice-service ]; then syft dir:./voice-service -o cyclonedx-json > sbom-voice.json fi echo "SBOMs generated successfully" when: - event: tag - event: manual vulnerability-scan: image: *golang_image commands: - | echo "Installing grype for ARM64..." wget -qO- https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin grype sbom:sbom-consent.json -o table --fail-on critical || true grype sbom:sbom-backend.json -o table --fail-on critical || true if [ -f sbom-voice.json ]; then grype sbom:sbom-voice.json -o table --fail-on critical || true fi when: - event: tag - event: manual depends_on: - generate-sbom # ======================================== # STAGE 5: Deploy (nur manuell) # ======================================== deploy-production: image: *docker_image commands: - echo "Deploying to production..." - docker compose -f docker-compose.yml pull || true - docker compose -f docker-compose.yml up -d --remove-orphans || true when: event: manual depends_on: - build-consent-service - build-backend