name: Test Suite # develop: push/PR → Tests gegen Dev (parallel oder vor Deploy Development). # main: kein push/PR-Trigger — vermeidet doppelten Dev-Lauf beim Merge develop→main; # Prod-Tests nur via workflow_run nach erfolgreichem Deploy Production. on: push: branches: [develop] pull_request: branches: [develop] workflow_run: workflows: ["Deploy Development", "Deploy Production"] types: [completed] jobs: # Pytest im laufenden backend-Container; ACCESS_LAYER + TRAINING_PLANNING Integration gegen dieselbe PostgreSQL wie Deploy (Schema via Container-Start migriert). pytest-backend: if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Backend pytest im deployten Container run: | set -e EVENT_NAME="${{ github.event_name }}" REF_NAME="${{ github.ref_name }}" BASE_REF="${{ github.base_ref }}" RUN_WORKFLOW="${{ github.event.workflow_run.name }}" APP_DIR="/home/lars/docker/shinkan" COMPOSE_FILE="docker-compose.yml" if [ "$EVENT_NAME" = "workflow_run" ]; then if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then APP_DIR="/home/lars/docker/shinkan-dev" COMPOSE_FILE="docker-compose.dev-env.yml" fi elif [ "$REF_NAME" = "develop" ] || [ "$BASE_REF" = "develop" ]; then APP_DIR="/home/lars/docker/shinkan-dev" COMPOSE_FILE="docker-compose.dev-env.yml" fi cd "$APP_DIR" echo "Warte auf stabilen backend-Container …" for i in $(seq 1 60); do if docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then echo "Backend bereit (Versuch $i)" break fi if [ "$i" -eq 60 ]; then echo "Timeout: backend-Container nicht bereit" docker compose -f "$COMPOSE_FILE" ps || true docker compose -f "$COMPOSE_FILE" logs backend --tail 80 || true exit 1 fi sleep 5 done docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc " pip install -r /app/requirements-dev.txt && cd /app && ACCESS_LAYER_STRICT=1 python scripts/check_access_layer_hints.py && python scripts/security_release_checks.py && ACCESS_LAYER_INTEGRATION=1 TRAINING_PLANNING_INTEGRATION=1 SKIP_DB_MIGRATE=1 python -m pytest tests -m 'not slow' -ra -vv --tb=short " lint-backend: if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Check backend syntax run: | EVENT_NAME="${{ github.event_name }}" REF_NAME="${{ github.ref_name }}" RUN_WORKFLOW="${{ github.event.workflow_run.name }}" APP_DIR="/home/lars/docker/shinkan" if [ "$EVENT_NAME" = "workflow_run" ]; then if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then APP_DIR="/home/lars/docker/shinkan-dev" fi elif [ "$REF_NAME" = "develop" ]; then APP_DIR="/home/lars/docker/shinkan-dev" fi python3 -m py_compile "$APP_DIR/backend/main.py" echo "✓ Backend syntax OK" build-frontend: if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Build frontend run: | EVENT_NAME="${{ github.event_name }}" REF_NAME="${{ github.ref_name }}" RUN_WORKFLOW="${{ github.event.workflow_run.name }}" APP_DIR="/home/lars/docker/shinkan" if [ "$EVENT_NAME" = "workflow_run" ]; then if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then APP_DIR="/home/lars/docker/shinkan-dev" fi elif [ "$REF_NAME" = "develop" ]; then APP_DIR="/home/lars/docker/shinkan-dev" fi cd "$APP_DIR/frontend" npm install npm run build echo "✓ Frontend build OK" # Phase-0 Lastsmoke: nur k6 — eigener Job (kein Node/Playwright), klare CI-Zuordnung. k6-health-baseline: name: k6 /health Baseline if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest env: E2E_TARGET_URL: https://dev.shinkan.jinkendo.de steps: - name: Checkout repository uses: actions/checkout@v4 - name: E2E-Ziel wählen (Dev über Proxy vs. Production) id: e2e run: | EVENT="${{ github.event_name }}" WF_NAME="${{ github.event.workflow_run.name }}" DEV_BASE="${{ env.E2E_TARGET_URL }}" if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then echo "mode=prod" >> $GITHUB_OUTPUT echo "base_url=https://shinkan.jinkendo.de" >> $GITHUB_OUTPUT echo "→ k6 gegen Prod-Basis." else echo "mode=dev" >> $GITHUB_OUTPUT echo "base_url=${DEV_BASE}" >> $GITHUB_OUTPUT echo "→ k6 gegen Dev (${DEV_BASE})." fi - name: Dev /health abwarten if: ${{ steps.e2e.outputs.mode == 'dev' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" echo "Warte auf $BASE/health …" for i in $(seq 1 90); do if curl -sf "$BASE/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 2 done echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen." curl -v "$BASE/health" || true exit 1 - name: Prod /health abwarten if: ${{ steps.e2e.outputs.mode == 'prod' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" echo "Warte auf $BASE/health …" for i in $(seq 1 60); do if curl -sf "$BASE/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 5 done echo "Timeout: Prod /health nicht erreichbar" curl -v "$BASE/health" || true exit 1 - name: Install k6 run: | set -e K6_VER="v0.55.0" ARCH=$(uname -m) case "$ARCH" in x86_64) K6_ARCH=amd64 ;; aarch64|arm64) K6_ARCH=arm64 ;; *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; esac echo "Installing k6 ${K6_VER} linux-${K6_ARCH}" curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz tar -xzf /tmp/k6.tgz -C /tmp sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 k6 version - name: k6 Health-Baseline (parallele /health) env: BASE_URL: ${{ steps.e2e.outputs.base_url }} run: | set -e echo "k6 gegen BASE_URL=$BASE_URL" k6 run scripts/load/k6-health-baseline.js echo "✓ k6 Health-Baseline passed" playwright-tests: if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest # Kein zusätzlicher docker-compose im Job: keine zweiten Container/Host-Ports. # Dev: Tests gegen bereits deployte URL (HTTPS, Reverse-Proxy). Prod: gleicher Ablauf. env: # Öffentliche Dev-Basis — muss ALLOWED_ORIGINS / Nginx entsprechen; bei anderer Domain Workflow anpassen. E2E_TARGET_URL: https://dev.shinkan.jinkendo.de steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: E2E-Ziel wählen (Dev über Proxy vs. Production) id: e2e run: | EVENT="${{ github.event_name }}" WF_NAME="${{ github.event.workflow_run.name }}" DEV_BASE="${{ env.E2E_TARGET_URL }}" if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then echo "mode=prod" >> $GITHUB_OUTPUT echo "base_url=https://shinkan.jinkendo.de" >> $GITHUB_OUTPUT echo "→ Prod. Secrets: E2E_PROD_TEST_EMAIL / E2E_PROD_TEST_PASSWORD." else echo "mode=dev" >> $GITHUB_OUTPUT echo "base_url=${DEV_BASE}" >> $GITHUB_OUTPUT echo "→ Deployte Dev-Umgebung (${DEV_BASE}). Secrets: E2E_DEV_TEST_EMAIL / E2E_DEV_TEST_PASSWORD." fi - name: Dev /health abwarten if: ${{ steps.e2e.outputs.mode == 'dev' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" echo "Warte auf $BASE/health …" for i in $(seq 1 90); do if curl -sf "$BASE/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 2 done echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen." curl -v "$BASE/health" || true exit 1 - name: Prod /health abwarten if: ${{ steps.e2e.outputs.mode == 'prod' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" echo "Warte auf $BASE/health …" for i in $(seq 1 60); do if curl -sf "$BASE/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 5 done echo "Timeout: Prod /health nicht erreichbar" curl -v "$BASE/health" || true exit 1 - name: Testnutzer registrieren (Dev, nur wenn möglich) if: ${{ steps.e2e.outputs.mode == 'dev' }} env: E2E_DEV_TEST_EMAIL: ${{ secrets.E2E_DEV_TEST_EMAIL }} E2E_DEV_TEST_PASSWORD: ${{ secrets.E2E_DEV_TEST_PASSWORD }} run: | BASE="${{ steps.e2e.outputs.base_url }}" if [ -z "$E2E_DEV_TEST_EMAIL" ] || [ -z "$E2E_DEV_TEST_PASSWORD" ]; then echo "(Registrierung übersprungen — Secrets E2E_DEV_* nicht gesetzt.)" exit 0 fi curl -sf -X POST "$BASE/api/auth/register" \ -H "Content-Type: application/json" \ -d "{\"email\":\"${E2E_DEV_TEST_EMAIL}\",\"password\":\"${E2E_DEV_TEST_PASSWORD}\",\"name\":\"Playwright CI\"}" \ || echo "(Register evtl. schon erfolgt oder Limits — Login-Test gilt trotzdem.)" - name: Install Playwright run: | npm ci || npm install npx playwright install --with-deps chromium - name: Run Playwright tests env: E2E_DEV_TEST_EMAIL: ${{ secrets.E2E_DEV_TEST_EMAIL }} E2E_DEV_TEST_PASSWORD: ${{ secrets.E2E_DEV_TEST_PASSWORD }} run: | set -e MODE="${{ steps.e2e.outputs.mode }}" BASE_URL="${{ steps.e2e.outputs.base_url }}" export PLAYWRIGHT_BASE_URL="$BASE_URL" if [ "$MODE" = "prod" ]; then export TEST_EMAIL="${{ secrets.E2E_PROD_TEST_EMAIL }}" export TEST_PASSWORD="${{ secrets.E2E_PROD_TEST_PASSWORD }}" if [ -z "$TEST_EMAIL" ] || [ -z "$TEST_PASSWORD" ]; then echo "Fehler: E2E_PROD_TEST_EMAIL und E2E_PROD_TEST_PASSWORD setzen." exit 1 fi else export TEST_EMAIL="$E2E_DEV_TEST_EMAIL" export TEST_PASSWORD="$E2E_DEV_TEST_PASSWORD" if [ -z "$TEST_EMAIL" ] || [ -z "$TEST_PASSWORD" ]; then echo "Fehler: E2E_DEV_TEST_EMAIL und E2E_DEV_TEST_PASSWORD setzen (Playwright soll gegen Dev einloggen)." exit 1 fi fi mkdir -p screenshots npx playwright test echo "✓ Playwright tests passed" - name: Upload test screenshots if: failure() # v4 ist auf Gitea (GHES-kompatibles Actions-Backend) oft nicht verfügbar uses: actions/upload-artifact@v3 with: name: playwright-screenshots path: screenshots/ retention-days: 7