#!/bin/bash # Startet web/serve.py temporaer, testet die wichtigsten Endpunkte, # raeumt den Prozess auch bei Abbruch auf. Ein einziger Aufruf; der # Agent muss nichts ueber Hintergrund-Jobs wissen. # # Nutzung: ./scripts/smoke_test.sh # Port 8082 # ./scripts/smoke_test.sh 9000 # anderer Port # Exit-Code: 0 = alle Endpunkte 200, sonst 1. set -euo pipefail PORT="${1:-8082}" [[ "$PORT" =~ ^[0-9]+$ ]] || { echo "Usage: $0 [port]" >&2; exit 1; } ROOT="$(cd "$(dirname "$0")/.." && pwd)" LOG="/tmp/smoke.$$.log" EXIT_CODE=1 MARKER="SMOKE_TEST_SERVER_$PORT" TIMEOUT=30 START_TIME=$(date +%s) kill_smoke_instances() { if pgrep -f "$MARKER" >/dev/null 2>&1; then pkill -TERM -f "$MARKER" 2>/dev/null || true sleep 0.5 if pgrep -f "$MARKER" >/dev/null 2>&1; then pkill -9 -f "$MARKER" 2>/dev/null || true fi fi } kill_smoke_instances # Pre-Check: serve.py muss sich kompilieren lassen. Sonst stirbt der # Server beim Import und das Polling unten wartet umsonst — wir wollen # in <100ms mit klarer Fehlermeldung scheitern, nicht in 22s mit # "Server nicht erreichbar". cd "$ROOT" if ! python3 -m py_compile web/serve.py 2> "$LOG"; then echo "FAIL: web/serve.py hat Syntax-/Indent-Fehler" echo "--- $LOG ---" cat "$LOG" exit 1 fi SERVER_SCRIPT=$(mktemp /tmp/smoke_server.XXXXXX.py) cat > "$SERVER_SCRIPT" << 'PYEOF' import sys, os, socketserver, logging sys.argv[0] = "SMOKE_TEST_SERVER_MARKER" sys.path.insert(0, os.path.join(os.getcwd(), 'web')) from serve import Handler port = PORT_PLACEHOLDER socketserver.TCPServer.allow_reuse_address = True logging.basicConfig(level=logging.WARNING) with socketserver.TCPServer(("", port), Handler) as httpd: httpd.serve_forever() PYEOF sed -i "s/PORT_PLACEHOLDER/$PORT/" "$SERVER_SCRIPT" python3 "$SERVER_SCRIPT" > "$LOG" 2>&1 & PID=$! cleanup() { kill "$PID" 2>/dev/null || true wait "$PID" 2>/dev/null || true kill_smoke_instances rm -f "$SERVER_SCRIPT" exit "$EXIT_CODE" } trap cleanup EXIT INT TERM # Auf Port-Binding warten (max. 2s). # 127.0.0.1 statt localhost umgeht den Resolver — sonst kann unter # WSL2/IPv6 ein TCP-SYN auf einen unbenutzten Port minutenlang ohne # RST hängen. --connect-timeout 1 ist Defense-in-Depth. for i in $(seq 1 50); do if (( $(date +%s) - START_TIME >= TIMEOUT )); then break fi if curl -sf --connect-timeout 1 -o /dev/null "http://127.0.0.1:$PORT/"; then break; fi sleep 0.1 done # Wenn nicht erreichbar: Log ausgeben und fail if ! curl -sf --connect-timeout 1 -o /dev/null "http://127.0.0.1:$PORT/"; then echo "FAIL: Server nicht erreichbar auf Port $PORT" echo "--- $LOG ---" cat "$LOG" EXIT_CODE=1 exit 1 fi # Endpunkte testen FAIL=0 for p in "/" "/index.html" "/templates.json" \ "/templates/system/commit_analysis.json" \ "/templates/system/code_reviewer.json" \ "/templates/system/summarizer.json" \ "/templates/user/email_draft.md"; do code=$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "http://127.0.0.1:$PORT$p") code="${code:-000}" status="ok" [ "$code" = "200" ] || { status="FAIL"; FAIL=1; } printf '%-50s %s %s\n' "$p" "$code" "$status" done # Inhalts-Gate: templates.json muss valides JSON sein. Ohne diese # Pruefung kann ein "Fix" das Manifest zerschiessen, der Server liefert # es weiterhin mit HTTP 200 aus, das Frontend stirbt aber beim # JSON.parse. Status-only-Tests sehen das nicht. if [ "$FAIL" -eq 0 ]; then if ! curl -s --max-time 5 "http://127.0.0.1:$PORT/templates.json" \ | python3 -c 'import json,sys; json.load(sys.stdin)' 2>&1; then echo "FAIL: /templates.json ist kein gueltiges JSON" FAIL=1 fi fi EXIT_CODE=$FAIL if [ "$FAIL" -eq 0 ]; then ENDPOINT_COUNT=7 # Anzahl der getesteten Endpunkte echo "--- alle $ENDPOINT_COUNT Endpunkte OK + templates.json valide ---" fi exit $FAIL