Team JWTs include `aud=mnemosyne` while per-turn JWTs omit `aud` entirely. Since `iss` + `typ` already partition the two token populations, explicitly skip audience verification to avoid rejecting valid tokens. Also expand test coverage for the MCP auth surface to exercise all three credential types (opaque MCPToken, per-turn JWT, team JWT), including replay cache behavior and Neo4j-backed library resolution via mocked cypher queries.
128 lines
4.0 KiB
Bash
Executable File
128 lines
4.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Run the Mnemosyne Django test suite against an ephemeral Postgres + Neo4j.
|
|
#
|
|
# Pattern borrowed from spelunker/test-postgres.sh. Both databases run as
|
|
# throwaway Docker containers on a private bridge network; each invocation
|
|
# gets a fresh pair. The Django test runner applies migrations to the
|
|
# empty Postgres and tears the whole network down on exit.
|
|
#
|
|
# Neo4j is included because a handful of library/tests/* modules touch
|
|
# neomodel at import time even though the assertions themselves stub the
|
|
# cypher layer; having a reachable bolt endpoint keeps startup probes
|
|
# from crashing.
|
|
#
|
|
# Usage:
|
|
# ./test-postgres.sh # run everything
|
|
# ./test-postgres.sh mcp_server # scope to one app
|
|
# ./test-postgres.sh mcp_server.tests.test_auth
|
|
# ./test-postgres.sh mcp_server -v 2 --failfast
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Configuration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
NET="mnemosyne-test-$$"
|
|
PG_CONTAINER="mnemosyne-test-pg-$$"
|
|
NEO_CONTAINER="mnemosyne-test-neo4j-$$"
|
|
PG_USER="mnemosyne"
|
|
PG_PASS="mnemosyne"
|
|
PG_DB="mnemosyne_test"
|
|
NEO_PASS="mnemosynetestpw" # Neo4j rejects short or obvious secrets.
|
|
|
|
IMAGE="git.helu.ca/r/mnemosyne:latest"
|
|
|
|
# Colours (skipped when not a TTY).
|
|
if [ -t 1 ]; then
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
|
else
|
|
GREEN=''; RED=''; YELLOW=''; NC=''
|
|
fi
|
|
|
|
say() { printf "${GREEN}==> %s${NC}\n" "$*"; }
|
|
warn() { printf "${YELLOW}[!] %s${NC}\n" "$*"; }
|
|
die() { printf "${RED}[✗] %s${NC}\n" "$*" >&2; exit 1; }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cleanup
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cleanup() {
|
|
say "Cleaning up test containers"
|
|
docker rm -f "$PG_CONTAINER" >/dev/null 2>&1 || true
|
|
docker rm -f "$NEO_CONTAINER" >/dev/null 2>&1 || true
|
|
docker network rm "$NET" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Start the support services
|
|
# ---------------------------------------------------------------------------
|
|
|
|
say "Creating Docker network $NET"
|
|
docker network create "$NET" >/dev/null
|
|
|
|
say "Starting Postgres ($PG_CONTAINER)"
|
|
docker run -d --rm \
|
|
--name "$PG_CONTAINER" \
|
|
--network "$NET" \
|
|
-e POSTGRES_USER="$PG_USER" \
|
|
-e POSTGRES_PASSWORD="$PG_PASS" \
|
|
-e POSTGRES_DB="$PG_DB" \
|
|
postgres:16-alpine \
|
|
>/dev/null
|
|
|
|
say "Starting Neo4j ($NEO_CONTAINER)"
|
|
docker run -d --rm \
|
|
--name "$NEO_CONTAINER" \
|
|
--network "$NET" \
|
|
-e NEO4J_AUTH="neo4j/$NEO_PASS" \
|
|
-e NEO4J_PLUGINS='["apoc"]' \
|
|
neo4j:5-community \
|
|
>/dev/null
|
|
|
|
# Wait for Postgres to accept connections.
|
|
say "Waiting for Postgres to become ready"
|
|
for i in $(seq 1 30); do
|
|
if docker exec "$PG_CONTAINER" pg_isready -U "$PG_USER" -d "$PG_DB" >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
docker exec "$PG_CONTAINER" pg_isready -U "$PG_USER" -d "$PG_DB" >/dev/null \
|
|
|| die "Postgres never became ready"
|
|
|
|
# Wait for Neo4j's HTTP endpoint — bolt comes up just after it.
|
|
say "Waiting for Neo4j to become ready"
|
|
for i in $(seq 1 60); do
|
|
if docker exec "$NEO_CONTAINER" wget -qO- http://localhost:7474/ >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Run the test command
|
|
# ---------------------------------------------------------------------------
|
|
|
|
SOURCE_DIR="$(cd "$(dirname "$0")" && pwd)/mnemosyne"
|
|
|
|
say "Running Django test suite"
|
|
docker run --rm \
|
|
--network "$NET" \
|
|
-v "$SOURCE_DIR":/app \
|
|
-w /app \
|
|
-e DJANGO_SETTINGS_MODULE=mnemosyne.settings \
|
|
-e APP_DB_NAME="$PG_DB" \
|
|
-e APP_DB_USER="$PG_USER" \
|
|
-e APP_DB_PASSWORD="$PG_PASS" \
|
|
-e DB_HOST="$PG_CONTAINER" \
|
|
-e DB_PORT=5432 \
|
|
-e NEOMODEL_NEO4J_BOLT_URL="bolt://neo4j:$NEO_PASS@$NEO_CONTAINER:7687" \
|
|
-e SECRET_KEY=test-only-not-a-real-secret \
|
|
-e DEBUG=0 \
|
|
-e MCP_REQUIRE_AUTH=True \
|
|
"$IMAGE" \
|
|
python manage.py test "$@"
|