prompt_template/scripts/smoke_test.sh
Michael 9890763f0f refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory

Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt

Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00

123 lines
4 KiB
Bash
Executable file

#!/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)
# Vorherige Instanzen dieses Smoke-Tests beenden
# Präziser: nur Prozesse mit exaktem Marker-String killen
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
# 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 im Hintergrund starten (exec → $! ist der Python-Prozess)
python3 -c "
import sys; sys.argv[0] = '$MARKER'
sys.path.insert(0, 'web')
import http.server, socketserver
from serve import Handler
port = $PORT
socketserver.TCPServer.allow_reuse_address = True
print(f'Serving on http://localhost:{port}', flush=True)
with socketserver.TCPServer(('', port), Handler) as httpd:
httpd.serve_forever()
" > "$LOG" 2>&1 &
PID=$!
cleanup() {
kill "$PID" 2>/dev/null || true
wait "$PID" 2>/dev/null || true
# Defensiv: falls der Kill-Signal den Python-Prozess nicht trifft
if pgrep -f "$MARKER" >/dev/null 2>&1; then
pkill -TERM -f "$MARKER" 2>/dev/null || true
sleep 0.3
if pgrep -f "$MARKER" >/dev/null 2>&1; then
pkill -9 -f "$MARKER" 2>/dev/null || true
fi
fi
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" \
"/templates/user/brainstorming.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=8 # Anzahl der getesteten Endpunkte
echo "--- alle $ENDPOINT_COUNT Endpunkte OK + templates.json valide ---"
fi
exit $FAIL