stubborn-otter/otter-boards

Otter Boards, a team task board you can run from the web or from Claude and ChatGPT. FastAPI + DataK3 + appid + Models + MCP.

A remix template · 1 remixes · Remix stubborn-otter/otter-boards

tests/e2e/run.sh

#!/usr/bin/env bash
# The real end-to-end run: real Postgres + schema.sql + the real repository, the real server, and
# a real MCP client. Needs Docker and app/.venv. Removes its container however it ends.
#
#   tests/e2e/run.sh          run everything and report
#   tests/e2e/run.sh serve    leave the server up at http://127.0.0.1:8765 to try by hand
#                             (AS=you@example.com picks who you are; Ctrl-C cleans up)
set -euo pipefail

MODE="${1:-check}"

ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
PY="$ROOT/app/.venv/bin/python"
NAME=otter-boards-e2e
PG_PORT=55432 APP_PORT=8765 MODELS_PORT=8766
export TEST_DATABASE_URL="postgresql://postgres:e2e@127.0.0.1:$PG_PORT/postgres"
export LOCAL_MODELS_BASE="http://127.0.0.1:$MODELS_PORT/v1"
export MODELS_DOWN_FLAG="$(mktemp -u)"
export APP_URL="http://127.0.0.1:$APP_PORT"
ME="${AS:-ana@otter.test}"
# Dodil's own code must have nothing to log in with, so a slip fails instead of calling out.
unset DODIL_SERVICE_ACCOUNT_ID DODIL_SERVICE_ACCOUNT_SECRET

PIDS=()
cleanup() {
  for pid in "${PIDS[@]:-}"; do [ -n "$pid" ] && kill "$pid" 2>/dev/null || true; done
  docker rm -f "$NAME" >/dev/null 2>&1 || true
  rm -f "$MODELS_DOWN_FLAG"
}
trap cleanup EXIT
trap 'exit 130' INT TERM

echo "== Postgres"
docker rm -f "$NAME" >/dev/null 2>&1 || true
docker run -d --rm --name "$NAME" -e POSTGRES_PASSWORD=e2e -p "127.0.0.1:$PG_PORT:5432" \
  postgres:16-alpine >/dev/null
for _ in $(seq 1 60); do
  docker exec "$NAME" pg_isready -q -U postgres -h 127.0.0.1 && break
  sleep 1
done
docker exec -i "$NAME" psql -q -U postgres -v ON_ERROR_STOP=1 < "$ROOT/schema.sql"
echo "schema.sql applied: $(docker exec "$NAME" psql -tA -U postgres -c \
  "select count(*) from information_schema.tables where table_schema='public'") tables"

if [ "$MODE" = check ]; then
  echo "== Rules suite, on the fake and on the real repository"
  (cd "$ROOT/app" && "$PY" -m pytest -q ../tests)
fi

echo "== Server"
"$PY" "$ROOT/tests/e2e/fake_models.py" "$MODELS_PORT" & PIDS+=($!)
(cd "$ROOT/app" && DEV_USER="$ME" PYTHONPATH=".:../tests/e2e" \
  exec "$PY" -m uvicorn local_app:app --port "$APP_PORT" --log-level warning) & PIDS+=($!)
for _ in $(seq 1 60); do
  curl -sf "$APP_URL/healthz" >/dev/null && break
  sleep 0.5
done

if [ "$MODE" = serve ]; then
  echo "Open $APP_URL as $ME. MCP is at $APP_URL/mcp. Ctrl-C stops it and removes the database."
  wait
  exit 0
fi

echo "== Over HTTP and MCP"
DEV_USER="$ME" "$PY" "$ROOT/tests/e2e/check.py"