Files
rose-ash/tests/test_server_config.sh
giles 17b6c872f2 Server cleanup: extract app-specific config into SX
Phase 1 Step 2 of architecture roadmap. The OCaml HTTP server is now
generic — all sx_docs-specific values (layout components, path prefix,
title, warmup paths, handler prefixes, CSS/JS, client libs) move into
sx/sx/app-config.sx as a __app-config dict. Server reads config at
startup with hardcoded defaults as fallback, so it works with no config,
partial config, or full config.

Removed: 9 demo data stubs, stepper cookie cache logic, page-functions.sx
directory heuristic. Added: 29-test server config test suite covering
standard, custom, no-config, and minimal-config scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 21:00:32 +00:00

256 lines
8.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Test suite for SX HTTP server app config handling.
# Starts the server with different __app-config values and verifies behavior.
set -euo pipefail
cd "$(dirname "$0")/.."
PROJECT_DIR="$(pwd)"
SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
BASE_PORT=8090
PASS=0
FAIL=0
ERRORS=""
SERVER_PID=""
CURRENT_PORT=""
# ── Helpers ──────────────────────────────────────────────────────────
TMP_DIR=$(mktemp -d)
cleanup() {
if [ -n "${SERVER_PID:-}" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
kill "$SERVER_PID" 2>/dev/null
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
start_server() {
local port="$1"
local extra_env="${2:-}"
CURRENT_PORT="$port"
# Kill anything on this port
kill $(lsof -ti :"$port") 2>/dev/null || true
sleep 1
# Start server
eval "$extra_env SX_PROJECT_DIR=$PROJECT_DIR $SERVER --http $port" \
> "$TMP_DIR/stdout" 2> "$TMP_DIR/stderr" &
SERVER_PID=$!
# Wait for "Listening" message
local tries=0
while ! grep -q "Listening" "$TMP_DIR/stderr" 2>/dev/null; do
sleep 1
tries=$((tries + 1))
if [ $tries -gt 120 ]; then
echo "TIMEOUT waiting for server on port $port"
cat "$TMP_DIR/stderr"
return 1
fi
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "Server died during startup on port $port"
cat "$TMP_DIR/stderr"
return 1
fi
done
echo " (server ready on port $port)"
}
stop_server() {
if [ -n "${SERVER_PID:-}" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
kill "$SERVER_PID" 2>/dev/null
wait "$SERVER_PID" 2>/dev/null || true
sleep 1
fi
SERVER_PID=""
}
url() { echo "http://localhost:$CURRENT_PORT$1"; }
assert_status() {
local desc="$1" path="$2" expected="$3"
shift 3
local actual
actual=$(curl -s -o /dev/null -w "%{http_code}" "$@" "$(url "$path")" 2>/dev/null)
if [ "$actual" = "$expected" ]; then
PASS=$((PASS + 1))
echo " PASS: $desc"
else
FAIL=$((FAIL + 1))
ERRORS="$ERRORS\n FAIL: $desc — expected $expected, got $actual"
echo " FAIL: $desc — expected $expected, got $actual"
fi
}
assert_redirect() {
local desc="$1" path="$2" expected_path="$3"
local actual
actual=$(curl -s -o /dev/null -w "%{redirect_url}" "$(url "$path")" 2>/dev/null)
local expected="http://localhost:$CURRENT_PORT$expected_path"
if [ "$actual" = "$expected" ]; then
PASS=$((PASS + 1))
echo " PASS: $desc"
else
FAIL=$((FAIL + 1))
ERRORS="$ERRORS\n FAIL: $desc — expected → $expected, got → $actual"
echo " FAIL: $desc — expected → $expected_path, got → $actual"
fi
}
assert_body_contains() {
local desc="$1" path="$2" expected="$3"
shift 3
curl -s "$@" "$(url "$path")" > "$TMP_DIR/body" 2>/dev/null
if grep -qF "$expected" "$TMP_DIR/body"; then
PASS=$((PASS + 1))
echo " PASS: $desc"
else
FAIL=$((FAIL + 1))
local size=$(wc -c < "$TMP_DIR/body")
ERRORS="$ERRORS\n FAIL: $desc — body ($size bytes) missing '$expected'"
echo " FAIL: $desc — body ($size bytes) missing '$expected'"
fi
}
assert_stderr_contains() {
local desc="$1" expected="$2"
if grep -qF "$expected" "$TMP_DIR/stderr" 2>/dev/null; then
PASS=$((PASS + 1))
echo " PASS: $desc"
else
FAIL=$((FAIL + 1))
ERRORS="$ERRORS\n FAIL: $desc — stderr missing '$expected'"
echo " FAIL: $desc — stderr missing '$expected'"
fi
}
assert_stderr_not_contains() {
local desc="$1" expected="$2"
if grep -qF "$expected" "$TMP_DIR/stderr" 2>/dev/null; then
FAIL=$((FAIL + 1))
ERRORS="$ERRORS\n FAIL: $desc — stderr should NOT contain '$expected'"
echo " FAIL: $desc — stderr should NOT contain '$expected'"
else
PASS=$((PASS + 1))
echo " PASS: $desc"
fi
}
# ── Setup ────────────────────────────────────────────────────────────
if [ ! -x "$SERVER" ]; then
echo "ERROR: Server binary not found at $SERVER"
echo "Run: cd hosts/ocaml && eval \$(opam env) && dune build"
exit 1
fi
echo "=== SX HTTP Server Config Tests ==="
echo ""
# ── Test Group 1: Default config (standard app-config.sx) ───────────
echo "── Group 1: Standard config ──"
start_server $((BASE_PORT + 1))
assert_stderr_contains "config loaded" "App config loaded: title=SX prefix=/sx/"
assert_stderr_contains "warmup ran 9 pages" "Pre-warmed 9 pages"
assert_stderr_not_contains "no stepper cookie refs" "sx-home-stepper"
assert_status "homepage 200" "/sx/" 200
assert_status "AJAX homepage 200" "/sx/" 200 -H "sx-request: true"
assert_status "geography 200" "/sx/(geography)" 200
assert_status "language 200" "/sx/(language)" 200
assert_status "applications 200" "/sx/(applications)" 200
assert_redirect "root → /sx/" "/" "/sx/"
assert_status "handler 200" "/sx/(api.spec-detail)?name=render-to-html" 200
assert_status "static 200" "/static/styles/tw.css" 200
assert_status "debug 200" "/sx/_debug/eval?expr=(%2B%201%202)" 200
assert_status "unknown 404" "/nope" 404
assert_body_contains "title is SX" "/sx/" "<title>SX</title>"
assert_body_contains "AJAX returns SX wire format" "/sx/" "sx-swap-oob" -H "sx-request: true"
assert_body_contains "debug eval result" "/sx/_debug/eval?expr=(%2B%201%202)" "3"
stop_server
echo ""
# ── Test Group 2: Custom title + minimal warmup ─────────────────────
echo "── Group 2: Custom title ──"
mkdir -p "$TMP_DIR/custom-sx"
cp -r "$PROJECT_DIR/sx/sx/"* "$TMP_DIR/custom-sx/"
cat > "$TMP_DIR/custom-sx/app-config.sx" << 'SXEOF'
(define __app-config
{:title "My Custom App"
:path-prefix "/sx/"
:home-path "/sx/"
:inner-layout "~layouts/doc"
:outer-layout "~shared:layout/app-body"
:shell "~shared:shell/sx-page-shell"
:client-libs (list "tw-layout.sx" "tw-type.sx" "tw.sx")
:css-files (list "basics.css" "tw.css")
:batchable-helpers (list "highlight" "component-source")
:handler-prefixes (list "handler:ex-" "handler:reactive-" "handler:")
:warmup-paths (list "/sx/")
:init-script :default})
SXEOF
start_server $((BASE_PORT + 2)) "SX_COMPONENTS_DIR=$TMP_DIR/custom-sx"
assert_stderr_contains "custom title in log" "App config loaded: title=My Custom App"
assert_stderr_contains "warmup 1 page" "Pre-warmed 1 pages"
assert_status "homepage works" "/sx/" 200
assert_body_contains "custom title in HTML" "/sx/" "<title>My Custom App</title>"
stop_server
echo ""
# ── Test Group 3: No config (missing __app-config) ──────────────────
echo "── Group 3: No app config (defaults) ──"
mkdir -p "$TMP_DIR/noconfig-sx"
cp -r "$PROJECT_DIR/sx/sx/"* "$TMP_DIR/noconfig-sx/"
rm -f "$TMP_DIR/noconfig-sx/app-config.sx"
start_server $((BASE_PORT + 3)) "SX_COMPONENTS_DIR=$TMP_DIR/noconfig-sx"
assert_stderr_contains "defaults fallback" "No __app-config found, using defaults"
assert_status "homepage without config" "/sx/" 200
assert_redirect "root redirect without config" "/" "/sx/"
assert_body_contains "default title" "/sx/" "<title>SX</title>"
stop_server
echo ""
# ── Test Group 4: Minimal config (only title) ───────────────────────
echo "── Group 4: Minimal config ──"
mkdir -p "$TMP_DIR/minimal-sx"
cp -r "$PROJECT_DIR/sx/sx/"* "$TMP_DIR/minimal-sx/"
cat > "$TMP_DIR/minimal-sx/app-config.sx" << 'SXEOF'
(define __app-config {:title "Bare"})
SXEOF
start_server $((BASE_PORT + 4)) "SX_COMPONENTS_DIR=$TMP_DIR/minimal-sx"
assert_stderr_contains "minimal config log" "App config loaded: title=Bare"
assert_status "homepage with minimal config" "/sx/" 200
assert_body_contains "bare title" "/sx/" "<title>Bare</title>"
# Defaults should still work for everything not specified
assert_redirect "default redirect" "/" "/sx/"
assert_status "default debug" "/sx/_debug/eval?expr=1" 200
stop_server
echo ""
# ── Summary ──────────────────────────────────────────────────────────
echo "=== Results: $PASS passed, $FAIL failed ==="
if [ $FAIL -gt 0 ]; then
echo -e "\nFailures:$ERRORS"
exit 1
else
echo "All tests passed."
exit 0
fi