Files
theta-suite/test-integration.sh

228 lines
9.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# test-integration.sh — Full Docker integration test for theta-suite.
#
# Starts the sso-manager container in test mode (no secrets.js required),
# seeds an LDAP test user, runs the full jest suite inside the container,
# then tears everything down.
#
# Usage:
# ./test-integration.sh # run all tests
# ./test-integration.sh --no-build # skip docker build (reuse existing image)
# ./test-integration.sh --keep # leave containers up after tests (for debugging)
set -euo pipefail
# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[test]${NC} $*"; }
ok() { echo -e "${GREEN}[✓]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
fail() { echo -e "${RED}[✗]${NC} $*"; exit 1; }
# ── Options ───────────────────────────────────────────────────────────────────
NO_BUILD=0; KEEP=0
for arg in "$@"; do
case "$arg" in
--no-build) NO_BUILD=1 ;;
--keep) KEEP=1 ;;
--help|-h) echo "Usage: $0 [--no-build] [--keep]"; exit 0 ;;
*) warn "Unknown option: $arg" ;;
esac
done
# ── Test environment config (self-contained, no secrets.js needed) ────────────
export COMPOSE_PROJECT_NAME="theta-test"
TEST_CONTAINER="theta-test-sso-manager-1"
LDAP_BASE_DN="dc=test,dc=local"
LDAP_ADMIN_PASS="testadminpass"
TEST_UID="test"
TEST_PASSWORD="MyTestPassword!2" # must match tests/setup.js TEST_CREDS
# ── Cleanup on exit ───────────────────────────────────────────────────────────
cleanup() {
local exit_code=$?
if [[ "$KEEP" == "1" ]]; then
warn "Leaving containers up (--keep). Tear down with: docker compose -p theta-test down -v"
else
info "Tearing down test stack..."
docker compose -p theta-test -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true
fi
exit $exit_code
}
trap cleanup EXIT INT TERM
# ── Write a minimal test compose override ────────────────────────────────────
info "Writing docker-compose.test.yml..."
cat > docker-compose.test.yml <<'COMPOSEEOF'
# Minimal test stack: sso-manager only (no proxy, no openbao, no jump-host).
# Uses env-mode config — no secrets.js or openbao token required.
services:
sso-manager:
build:
context: ./sso-manager-node
dockerfile: Dockerfile.openldap
target: ""
container_name: theta-test-sso-manager
restart: "no"
networks: [theta-test-net]
environment:
- NODE_ENV=test
- NODE_PORT=3001
- LDAP_BASE_DN=dc=test,dc=local
- LDAP_ADMIN_PASS=testadminpass
- ORG_NAME=Test Org
- LDAP_DOMAIN=test.local
# Inline JWT secret for tests (no secrets.js or bao needed)
- app_oauth__jwtSecret=test-integration-jwt-secret-theta42
ports:
- "13001:3001"
- "10389:389"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health"]
interval: 5s
timeout: 5s
retries: 24
start_period: 30s
networks:
theta-test-net:
driver: bridge
COMPOSEEOF
# ── Build ─────────────────────────────────────────────────────────────────────
if [[ "$NO_BUILD" == "0" ]]; then
info "Building sso-manager test image..."
docker compose -p theta-test -f docker-compose.test.yml build sso-manager
ok "Image built"
else
warn "Skipping build (--no-build)"
fi
# ── Start ─────────────────────────────────────────────────────────────────────
info "Starting sso-manager container..."
docker compose -p theta-test -f docker-compose.test.yml up -d sso-manager
# ── Wait for healthy ──────────────────────────────────────────────────────────
info "Waiting for sso-manager to become healthy (up to 120s)..."
for i in $(seq 1 120); do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' theta-test-sso-manager 2>/dev/null || echo "missing")
if [[ "$STATUS" == "healthy" ]]; then
ok "sso-manager is healthy"
break
fi
if [[ $i -eq 120 ]]; then
warn "Container never became healthy. Logs:"
docker logs theta-test-sso-manager --tail 60
fail "sso-manager failed to become healthy after 120s"
fi
sleep 1
done
# ── Wait for LDAP ─────────────────────────────────────────────────────────────
info "Waiting for LDAP on port 10389..."
for i in $(seq 1 30); do
if ldapsearch -x -H ldap://localhost:10389 -b "" -s base "(objectClass=*)" >/dev/null 2>&1; then
ok "LDAP is ready"
break
fi
if [[ $i -eq 30 ]]; then
fail "LDAP did not become reachable on localhost:10389 after 30s"
fi
sleep 1
done
# ── Seed test user ────────────────────────────────────────────────────────────
info "Seeding test LDAP user via seed-test-user.sh..."
docker cp sso-manager-node/test/seed-test-user.sh theta-test-sso-manager:/tmp/seed-test-user.sh
docker exec \
-e LDAP_HOST=localhost \
-e LDAP_PORT=389 \
-e BIND_DN="cn=admin,${LDAP_BASE_DN}" \
-e BIND_PW="${LDAP_ADMIN_PASS}" \
-e BASE_DN="${LDAP_BASE_DN}" \
theta-test-sso-manager \
sh /tmp/seed-test-user.sh
ok "Test user seeded"
# ── Install dev deps (jest) inside the running container ──────────────────────
info "Installing test dependencies (jest) inside container..."
docker exec theta-test-sso-manager sh -c "
cd /app &&
if ! command -v jest >/dev/null 2>&1 && [ ! -f node_modules/.bin/jest ]; then
npm install --save-dev jest@latest supertest@latest --silent 2>&1 | tail -3
else
echo 'jest already installed'
fi
"
ok "Test deps ready"
# ── Copy test files into container ────────────────────────────────────────────
info "Copying tests into container..."
docker cp sso-manager-node/nodejs/tests/. theta-test-sso-manager:/app/tests/
ok "Tests copied"
# ── Run jest ──────────────────────────────────────────────────────────────────
info "Running full jest test suite inside container..."
echo ""
# These app_* vars are set by the entrypoint for the main process but NOT
# inherited by docker exec subprocesses. Pass them explicitly so the jest
# process loads app.js with the correct LDAP connection details.
docker exec \
-e NODE_ENV=test \
-e REDIS_URL="redis://127.0.0.1:6379" \
-e app_oauth__jwtSecret="test-integration-jwt-secret-theta42" \
-e app_ldap__url="ldap://localhost:389" \
-e app_ldap__bindDN="cn=admin,${LDAP_BASE_DN}" \
-e app_ldap__bindPassword="${LDAP_ADMIN_PASS}" \
-e app_ldap__userBase="ou=people,${LDAP_BASE_DN}" \
-e app_ldap__groupBase="ou=groups,${LDAP_BASE_DN}" \
theta-test-sso-manager \
sh -c "
cd /app
# Write test conf with full LDAP connection details so jest workers get
# the correct config without needing to inherit docker exec env vars.
# app_* env vars are only applied at conf-module require time, but jest
# workers may not reliably inherit them across all parallelism models.
cat > /app/conf/test.js << CONFEOF
'use strict';
module.exports = {
redis: { prefix: 'sso_manager_test_' },
oauth: { jwtSecret: 'test-integration-jwt-secret-theta42' },
ldap: {
url: 'ldap://localhost:389',
bindDN: 'cn=admin,${LDAP_BASE_DN}',
bindPassword: '${LDAP_ADMIN_PASS}',
userBase: 'ou=people,${LDAP_BASE_DN}',
groupBase: 'ou=groups,${LDAP_BASE_DN}'
}
};
CONFEOF
echo 'conf/test.js written'
REDIS_URL='redis://127.0.0.1:6379' node_modules/.bin/jest --forceExit --passWithNoTests 2>&1
"
JEST_EXIT=$?
echo ""
if [[ $JEST_EXIT -eq 0 ]]; then
ok "All jest tests passed!"
else
fail "Some jest tests failed (exit code $JEST_EXIT)"
fi
# ── Theta-agent Go tests (host-side, no docker needed) ───────────────────────
if command -v go >/dev/null 2>&1 && [[ -d theta-agent ]]; then
info "Running theta-agent Go tests..."
(cd theta-agent && go test ./... -count=1 2>&1)
ok "Theta-agent Go tests passed"
else
warn "Skipping theta-agent Go tests (go not found or theta-agent dir missing)"
fi
ok "All integration tests complete!"