diff --git a/package.json b/package.json index 5a721af..138e93b 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "format": "prettier --write .", "test": "turbo test", "test:integration": "pnpm --filter @sammo-ts/integration-tests test:integration", + "test:integration:conditional": "./tools/run-conditional-integration.sh", "build": "turbo build", "typecheck": "turbo typecheck", "dev": "turbo dev", diff --git a/tools/run-conditional-integration.sh b/tools/run-conditional-integration.sh new file mode 100755 index 0000000..64d20f7 --- /dev/null +++ b/tools/run-conditional-integration.sh @@ -0,0 +1,122 @@ +#!/bin/sh +set -eu + +usage() { + echo "usage: $0 [env-file]" >&2 + exit 64 +} + +[ "$#" -le 1 ] || usage + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +workspace_root=$(CDPATH= cd -- "$script_dir/.." && pwd) +env_file=${1:-"$workspace_root/.env.ci"} + +if [ ! -f "$env_file" ]; then + echo "missing integration environment file: $env_file" >&2 + exit 66 +fi +env_file=$(CDPATH= cd -- "$(dirname -- "$env_file")" && pwd)/$(basename -- "$env_file") + +# .env.ci is generated by the sam_rebuild development Compose helper and is a +# shell-compatible, credential-bearing local file. Never print its contents. +set -a +# shellcheck disable=SC1090 +. "$env_file" +set +a + +: "${POSTGRES_HOST:?POSTGRES_HOST is required in $env_file}" +: "${POSTGRES_PORT:?POSTGRES_PORT is required in $env_file}" +: "${POSTGRES_DB:?POSTGRES_DB is required in $env_file}" +: "${POSTGRES_USER:?POSTGRES_USER is required in $env_file}" +: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required in $env_file}" +: "${REDIS_URL:?REDIS_URL is required in $env_file}" + +integration_schema=${CONDITIONAL_INTEGRATION_SCHEMA:-conditional_integration} +scenario_schema=${SCENARIO_SEED_INTEGRATION_SCHEMA:-conditional_scenario_seed} + +for schema in "$integration_schema" "$scenario_schema"; do + case "$schema" in + ''|[!a-z_]*|*[!a-z0-9_]*) + echo "integration schema must be a lowercase PostgreSQL identifier: $schema" >&2 + exit 64 + ;; + esac +done + +build_database_url() { + SCHEMA_NAME=$1 node --input-type=module -e ' + const required = [ + "POSTGRES_HOST", + "POSTGRES_PORT", + "POSTGRES_DB", + "POSTGRES_USER", + "POSTGRES_PASSWORD", + "SCHEMA_NAME", + ]; + for (const name of required) { + if (!process.env[name]) { + throw new Error(`missing ${name}`); + } + } + const url = new URL("postgresql://localhost"); + url.hostname = process.env.POSTGRES_HOST; + url.port = process.env.POSTGRES_PORT; + url.username = process.env.POSTGRES_USER; + url.password = process.env.POSTGRES_PASSWORD; + url.pathname = `/${process.env.POSTGRES_DB}`; + url.searchParams.set("schema", process.env.SCHEMA_NAME); + process.stdout.write(url.href); + ' +} + +run_marked_tests() { + package_dir=$1 + marker=$2 + label=$3 + test_files=$(cd "$workspace_root/$package_dir" && rg -l "$marker" test -g '*.test.ts' | sort) + if [ -z "$test_files" ]; then + echo "no $label tests found under $package_dir" >&2 + exit 65 + fi + echo "running $label tests in $package_dir" + ( + cd "$workspace_root/$package_dir" + printf '%s\n' "$test_files" | + xargs -r pnpm exec vitest run --no-file-parallelism --maxWorkers=1 + ) +} + +cd "$workspace_root" +pnpm install --frozen-lockfile +pnpm --filter @sammo-ts/infra prisma:generate +pnpm --filter @sammo-ts/common build + +database_url=$(build_database_url "$integration_schema") +export POSTGRES_SCHEMA=$integration_schema +export DATABASE_URL=$database_url +export INPUT_EVENT_DATABASE_URL=$database_url +export GENERAL_LIFECYCLE_DATABASE_URL=$database_url +export TURN_DAEMON_LEASE_DATABASE_URL=$database_url +export TURN_DIFFERENTIAL_DATABASE_URL=$database_url + +pnpm --filter @sammo-ts/infra prisma:db:push:game + +database_markers='INPUT_EVENT_DATABASE_URL|GENERAL_LIFECYCLE_DATABASE_URL|TURN_DAEMON_LEASE_DATABASE_URL' +run_marked_tests app/game-api "$database_markers" "PostgreSQL" +run_marked_tests app/game-engine "$database_markers" "PostgreSQL" +run_marked_tests tools/integration-tests \ + 'TURN_DIFFERENTIAL_DATABASE_URL|INPUT_EVENT_DATABASE_URL' \ + "PostgreSQL snapshot" +run_marked_tests app/game-api 'process\.env\.REDIS_URL' "Redis" + +scenario_database_url=$(build_database_url "$scenario_schema") +( + export POSTGRES_SCHEMA=$scenario_schema + export DATABASE_URL=$scenario_database_url + pnpm --filter @sammo-ts/infra prisma:db:push:game + cd "$workspace_root/app/game-engine" + pnpm exec vitest run --no-file-parallelism --maxWorkers=1 test/scenarioSeeder.test.ts +) + +echo "conditional PostgreSQL and Redis integration tests passed"