#!/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} npc_possession_schema=${NPC_POSSESSION_INTEGRATION_SCHEMA:-conditional_$(date +%s)_$$_npc_possession_integration} npc_possession_differential_schema=conditional_$(date +%s)_$$_npc_possession_differential for schema in "$integration_schema" "$scenario_schema" "$npc_possession_schema" "$npc_possession_differential_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 pnpm --filter @sammo-ts/infra 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 export RESERVED_TURN_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|RESERVED_TURN_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" npc_possession_database_url=$(build_database_url "$npc_possession_schema") ( export POSTGRES_SCHEMA=$npc_possession_schema export DATABASE_URL=$npc_possession_database_url export NPC_POSSESSION_DATABASE_URL=$npc_possession_database_url pnpm --filter @sammo-ts/infra prisma:migrate:deploy:game pnpm --filter @sammo-ts/infra prisma:migrate:deploy:game run_marked_tests app/game-api \ 'NPC_POSSESSION_DATABASE_URL' \ "NPC possession PostgreSQL" ) if [ "${TURN_DIFFERENTIAL_REFERENCE:-}" = "1" ]; then reference_workspace_root=${TURN_DIFFERENTIAL_WORKSPACE_ROOT:-"$workspace_root/.."} reference_stack=${TURN_DIFFERENTIAL_STACK_DIR:-"$reference_workspace_root/docker_compose_files/reference"} if [ ! -f "$reference_stack/compose.yml" ]; then echo "reference compose file is missing; set TURN_DIFFERENTIAL_WORKSPACE_ROOT or TURN_DIFFERENTIAL_STACK_DIR" >&2 exit 69 fi if [ ! -f "$reference_workspace_root/ref/sam/d_setting/RootDB.php" ] || [ ! -f "$reference_workspace_root/ref/sam/hwe/d_setting/DB.php" ]; then echo "reference runtime database configuration is missing under $reference_workspace_root/ref/sam" >&2 exit 69 fi if ! docker compose -f "$reference_stack/compose.yml" ps --status running --services | grep -qx db || ! docker compose -f "$reference_stack/compose.yml" exec -T db healthcheck.sh --connect --innodb_initialized \ >/dev/null 2>&1; then echo "TURN_DIFFERENTIAL_REFERENCE=1 requires a healthy bootstrapped reference db stack" >&2 exit 69 fi reference_tables=$( docker compose -f "$reference_stack/compose.yml" exec -T db sh -c ' export MYSQL_PWD="$(cat /run/secrets/db_root_password)" mariadb --batch --skip-column-names -u root -e " SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = '\''$MARIADB_DATABASE'\'' AND TABLE_NAME = '\''member'\''; SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = '\''$REF_HWE_DB_NAME'\'' AND TABLE_NAME IN ('\''general'\'', '\''select_npc_token'\''); " ' 2>/dev/null ) || { echo "failed to inspect reference database bootstrap state" >&2 exit 69 } if [ "$reference_tables" != "$(printf '1\n2')" ]; then echo "reference root/HWE databases are not bootstrapped" >&2 exit 69 fi npc_possession_differential_database_url=$(build_database_url "$npc_possession_differential_schema") ( cleanup_npc_possession_differential_schema() { printf 'DROP SCHEMA IF EXISTS "%s" CASCADE;\n' "$npc_possession_differential_schema" | DATABASE_URL=$npc_possession_differential_database_url \ pnpm --filter @sammo-ts/infra exec prisma db execute --stdin >/dev/null } handle_npc_possession_differential_exit() { exit_status=$? trap - EXIT HUP INT TERM if ! cleanup_npc_possession_differential_schema && [ "$exit_status" -eq 0 ]; then exit_status=1 fi exit "$exit_status" } trap handle_npc_possession_differential_exit EXIT trap 'exit 129' HUP trap 'exit 130' INT trap 'exit 143' TERM export POSTGRES_SCHEMA=$npc_possession_differential_schema export DATABASE_URL=$npc_possession_differential_database_url export NPC_POSSESSION_DIFFERENTIAL_DATABASE_URL=$npc_possession_differential_database_url export TURN_DIFFERENTIAL_WORKSPACE_ROOT=$reference_workspace_root export TURN_DIFFERENTIAL_STACK_DIR=$reference_stack pnpm --filter @sammo-ts/infra prisma:db:push:game cd "$workspace_root/tools/integration-tests" pnpm exec vitest run --no-file-parallelism --maxWorkers=1 \ test/npcPossessionSelectionReference.integration.test.ts ) live_sortie_schema=${LIVE_SORTIE_PERSISTENCE_SCHEMA:-conditional_live_sortie_persistence} case "$live_sortie_schema" in ''|[!a-z_]*|*[!a-z0-9_]*) echo "live sortie persistence schema must be a lowercase PostgreSQL identifier" >&2 exit 64 ;; esac live_sortie_database_url=$(build_database_url "$live_sortie_schema") export POSTGRES_SCHEMA=$live_sortie_schema export DATABASE_URL=$live_sortie_database_url export LIVE_SORTIE_PERSISTENCE_DATABASE_URL=$live_sortie_database_url pnpm --filter @sammo-ts/infra prisma:db:push:game run_marked_tests tools/integration-tests \ 'LIVE_SORTIE_PERSISTENCE_DATABASE_URL' \ "live sortie PostgreSQL persistence" export POSTGRES_SCHEMA=$integration_schema export DATABASE_URL=$database_url fi 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"