#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" TMP_ROOT="$(mktemp -d)" trap 'rm -rf "$TMP_ROOT"' EXIT PASS_COUNT=0 say() { printf '%s\n' "$*" } fail() { printf 'FAIL %s\n' "$*" >&2 exit 1 } assert_file_contains() { local file="$1" local pattern="$2" if ! grep -Fq "$pattern" "$file"; then fail "expected '$pattern' in $file" fi } assert_not_exists_or_empty() { local file="$1" if [ -e "$file" ] && [ -s "$file" ]; then fail "expected empty or missing file: $file" fi } new_fixture() { local name="$1" local dir="$TMP_ROOT/$name" mkdir -p "$dir/bin" "$dir/baron/config/.generated/ory/kratos" "$dir/baron/config/.generated/ory/oathkeeper" "$dir/logs" "$dir/state" touch "$dir/baron/Makefile" cat > "$dir/baron/config/.generated/ory/kratos/kratos.yml" <<'EOF' selfservice: allowed_return_urls: - http://localhost:5000 - [] EOF cat > "$dir/baron/config/.generated/ory/oathkeeper/entrypoint.sh" <<'EOF' #!/usr/bin/env sh exit 0 EOF cat > "$dir/bin/docker" <<'EOF' #!/usr/bin/env bash set -euo pipefail log_file="${TEST_CMD_LOG:?}" printf 'docker %s\n' "$*" >> "$log_file" case "$*" in "compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml up -d") if [ "${TEST_DOCKER_UP_FAIL_ONCE:-0}" = "1" ] && [ ! -f "${TEST_STATE_DIR:?}/docker-up.failed" ]; then touch "${TEST_STATE_DIR}/docker-up.failed" echo "name conflict" exit 1 fi if [ "${TEST_DOCKER_UP_HEALTH_FAILURE:-0}" = "1" ] && [ ! -f "${TEST_STATE_DIR:?}/docker-up.health-failed" ]; then touch "${TEST_STATE_DIR}/docker-up.health-failed" echo "dependency failed to start: container baron_backend is unhealthy" exit 1 fi echo "compose up ok" ;; "compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml down") echo "compose down ok" ;; "compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml logs --no-color") echo "fake compose logs" ;; "ps --format {{.Names}} {{.Status}}") printf 'baron_backend Up (healthy)\nory_kratos Up\n' ;; "ps -a --format {{.Names}}") printf 'old_baron_backend\nold_ory_kratos\n' ;; "ps --format {{.Names}}") printf 'baron_backend\nbaron_gateway\n' ;; "rm -f old_baron_backend old_ory_kratos") echo "removed stale containers" ;; *) ;; esac EOF cat > "$dir/bin/make" <<'EOF' #!/usr/bin/env bash set -euo pipefail printf 'make %s\n' "$*" >> "${TEST_CMD_LOG:?}" EOF cat > "$dir/bin/adb" <<'EOF' #!/usr/bin/env bash set -euo pipefail printf 'adb %s\n' "$*" >> "${TEST_CMD_LOG:?}" EOF cat > "$dir/bin/pkill" <<'EOF' #!/usr/bin/env bash set -euo pipefail printf 'pkill %s\n' "$*" >> "${TEST_CMD_LOG:?}" EOF cat > "$dir/bin/chmod" <<'EOF' #!/usr/bin/env bash set -euo pipefail printf 'chmod %s\n' "$*" >> "${TEST_CMD_LOG:?}" EOF cat > "$dir/bin/chown" <<'EOF' #!/usr/bin/env bash set -euo pipefail printf 'chown %s\n' "$*" >> "${TEST_CMD_LOG:?}" EOF cat > "$dir/check.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail state_dir="${TEST_STATE_DIR:?}" attempt_file="$state_dir/check-attempts" count=0 if [ -f "$attempt_file" ]; then count="$(cat "$attempt_file")" fi count=$((count + 1)) printf '%s' "$count" > "$attempt_file" mode="${CHECK_MODE:-pass}" case "$mode" in pass) echo "RESULT ready-ish: 0 failure(s), 0 warning(s)" ;; warn_then_pass) if [ "$count" -eq 1 ]; then echo "RESULT ready-ish: 0 failure(s), 1 warning(s)" else echo "RESULT ready-ish: 0 failure(s), 0 warning(s)" fi ;; fail) echo "RESULT blocked: 1 failure(s), 0 warning(s)" exit 1 ;; *) echo "unknown CHECK_MODE=$mode" >&2 exit 2 ;; esac EOF cat > "$dir/api-smoke.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail state_dir="${TEST_STATE_DIR:?}" attempt_file="$state_dir/api-attempts" count=0 if [ -f "$attempt_file" ]; then count="$(cat "$attempt_file")" fi count=$((count + 1)) printf '%s' "$count" > "$attempt_file" mode="${API_MODE:-pass}" case "$mode" in pass) echo "PASS api smoke" ;; login_failed) if [ "${TDC114_SKIP_AUTH_SMOKE:-false}" = "true" ]; then echo "SKIP authenticated smoke: TDC114_SKIP_AUTH_SMOKE=true" exit 0 fi echo 'FAIL phone-login success: HTTP 401' >&2 echo 'Response body:' >&2 echo '{"error":"Login failed","code":"login_failed"}' >&2 exit 1 ;; fail) echo "FAIL api smoke" >&2 exit 1 ;; *) echo "unknown API_MODE=$mode" >&2 exit 2 ;; esac EOF cat > "$dir/wsl-maintenance.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail mode="${1:-status}" case "$mode" in status) echo "${TEST_WSL_MAINTENANCE_OUTPUT:-WSL maintenance reminder: OK}" ;; *) echo "unsupported mode: $mode" >&2 exit 2 ;; esac EOF chmod +x "$dir/bin/docker" "$dir/bin/make" "$dir/bin/adb" "$dir/bin/pkill" \ "$dir/bin/chmod" "$dir/bin/chown" "$dir/check.sh" "$dir/api-smoke.sh" \ "$dir/wsl-maintenance.sh" "$dir/baron/config/.generated/ory/oathkeeper/entrypoint.sh" printf '%s\n' "$dir" } run_case() { local name="$1" shift say "CASE $name" "$@" PASS_COUNT=$((PASS_COUNT + 1)) } test_startup_dry_run() { local fixture fixture="$(new_fixture startup-dry-run)" local cmd_log="$fixture/commands.log" TEST_CMD_LOG="$cmd_log" \ TEST_STATE_DIR="$fixture/state" \ BARON_SSO_WORKTREE="$fixture/baron" \ TDC114_LOG_BASE="$fixture/logs" \ TDC114_CHECK_SCRIPT="$fixture/check.sh" \ TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \ TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \ TDC114_START_AUTH_SERVER=false \ DOCKER_BIN="$fixture/bin/docker" \ MAKE_BIN="$fixture/bin/make" \ bash "$ROOT_DIR/scripts/startup.sh" --dry-run --wait=0 --skip-android-check assert_not_exists_or_empty "$cmd_log" assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "WSL maintenance reminder: OK" assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "Dry-run mode: no actions were actually performed" } test_startup_auto_retry_success() { local fixture fixture="$(new_fixture startup-auto-success)" local cmd_log="$fixture/commands.log" TEST_CMD_LOG="$cmd_log" \ TEST_STATE_DIR="$fixture/state" \ TEST_DOCKER_UP_FAIL_ONCE=1 \ CHECK_MODE=warn_then_pass \ API_MODE=pass \ BARON_SSO_WORKTREE="$fixture/baron" \ TDC114_LOG_BASE="$fixture/logs" \ TDC114_CHECK_SCRIPT="$fixture/check.sh" \ TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \ TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \ TDC114_START_AUTH_SERVER=false \ DOCKER_BIN="$fixture/bin/docker" \ MAKE_BIN="$fixture/bin/make" \ bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check assert_file_contains "$cmd_log" "docker rm -f old_baron_backend old_ory_kratos" assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "check-baron-api-env.sh reported warnings on attempt 1" assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "check-baron-api-env.sh passed with zero warnings" assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke.sh passed" if grep -Fq " - []" "$fixture/baron/config/.generated/ory/kratos/kratos.yml"; then fail "expected startup to sanitize invalid empty allowed_return_urls entry" fi } test_startup_auto_health_failure_tolerated() { local fixture fixture="$(new_fixture startup-auto-health-failure)" local cmd_log="$fixture/commands.log" TEST_CMD_LOG="$cmd_log" \ TEST_STATE_DIR="$fixture/state" \ TEST_DOCKER_UP_HEALTH_FAILURE=1 \ CHECK_MODE=pass \ API_MODE=pass \ BARON_SSO_WORKTREE="$fixture/baron" \ TDC114_LOG_BASE="$fixture/logs" \ TDC114_CHECK_SCRIPT="$fixture/check.sh" \ TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \ TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \ TDC114_START_AUTH_SERVER=false \ DOCKER_BIN="$fixture/bin/docker" \ MAKE_BIN="$fixture/bin/make" \ bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "compose up reported dependency or health issues; continuing to stabilization checks before declaring failure" assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke.sh passed" } test_startup_login_failed_falls_back_to_base_smoke() { local fixture fixture="$(new_fixture startup-login-fallback)" local cmd_log="$fixture/commands.log" TEST_CMD_LOG="$cmd_log" \ TEST_STATE_DIR="$fixture/state" \ CHECK_MODE=pass \ API_MODE=login_failed \ BARON_SSO_WORKTREE="$fixture/baron" \ TDC114_LOG_BASE="$fixture/logs" \ TDC114_CHECK_SCRIPT="$fixture/check.sh" \ TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \ TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \ TDC114_START_AUTH_SERVER=false \ DOCKER_BIN="$fixture/bin/docker" \ MAKE_BIN="$fixture/bin/make" \ bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke authenticated login failed; retrying in base-smoke-only mode because TDC114_REQUIRE_AUTH_SMOKE is false" assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "api-smoke base-smoke-only mode passed" } test_startup_auto_failure() { local fixture fixture="$(new_fixture startup-auto-failure)" local cmd_log="$fixture/commands.log" set +e TEST_CMD_LOG="$cmd_log" \ TEST_STATE_DIR="$fixture/state" \ CHECK_MODE=fail \ API_MODE=pass \ BARON_SSO_WORKTREE="$fixture/baron" \ TDC114_LOG_BASE="$fixture/logs" \ TDC114_CHECK_SCRIPT="$fixture/check.sh" \ TDC114_API_SMOKE_SCRIPT="$fixture/api-smoke.sh" \ TDC114_WSL_MAINTENANCE_SCRIPT="$fixture/wsl-maintenance.sh" \ TDC114_START_AUTH_SERVER=false \ DOCKER_BIN="$fixture/bin/docker" \ MAKE_BIN="$fixture/bin/make" \ bash "$ROOT_DIR/scripts/startup.sh" --auto --wait=0 --skip-android-check local rc=$? set -e if [ "$rc" -eq 0 ]; then fail "startup auto failure case should exit non-zero" fi assert_file_contains "$fixture/logs/$(date +%F)/startup.log" "Startup failed: health checks did not pass" } test_wsl_maintenance_schedule() { local fixture fixture="$(new_fixture wsl-maintenance-schedule)" local state_dir="$fixture/state" TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \ TDC114_WSL_MAINTENANCE_ANCHOR_DATE=2026-07-16 \ TDC114_WSL_MAINTENANCE_TODAY=2026-07-20 \ bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" status > "$fixture/status-not-due.txt" assert_file_contains "$fixture/status-not-due.txt" "WSL maintenance reminder: OK" assert_file_contains "$fixture/status-not-due.txt" "Next due date: 2026-07-30" TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \ TDC114_WSL_MAINTENANCE_ANCHOR_DATE=2026-07-16 \ TDC114_WSL_MAINTENANCE_TODAY=2026-07-30 \ bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" status > "$fixture/status-due.txt" assert_file_contains "$fixture/status-due.txt" "WSL maintenance reminder: DUE" assert_file_contains "$fixture/status-due.txt" "./scripts/check-wsl-maintenance.sh record" TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \ TDC114_WSL_MAINTENANCE_TODAY=2026-07-30 \ bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" record > "$fixture/record.txt" assert_file_contains "$fixture/record.txt" "Recorded WSL maintenance date: 2026-07-30" TDC114_WSL_MAINTENANCE_STATE_DIR="$state_dir" \ TDC114_WSL_MAINTENANCE_ANCHOR_DATE=2026-07-16 \ TDC114_WSL_MAINTENANCE_TODAY=2026-08-05 \ bash "$ROOT_DIR/scripts/check-wsl-maintenance.sh" status > "$fixture/status-after-record.txt" assert_file_contains "$fixture/status-after-record.txt" "WSL maintenance reminder: OK" assert_file_contains "$fixture/status-after-record.txt" "Base date: 2026-07-30" } test_shutdown_dry_run() { local fixture fixture="$(new_fixture shutdown-dry-run)" local cmd_log="$fixture/commands.log" TEST_CMD_LOG="$cmd_log" \ TEST_STATE_DIR="$fixture/state" \ BARON_SSO_WORKTREE="$fixture/baron" \ TDC114_LOG_BASE="$fixture/logs" \ TDC114_ADB_CONNECT_ADDRESS=172.21.128.1:5562 \ DOCKER_BIN="$fixture/bin/docker" \ ADB_BIN="$fixture/bin/adb" \ PKILL_BIN="$fixture/bin/pkill" \ CHMOD_BIN="$fixture/bin/chmod" \ CHOWN_BIN="$fixture/bin/chown" \ bash "$ROOT_DIR/scripts/shutdown.sh" --dry-run assert_not_exists_or_empty "$cmd_log" assert_file_contains "$fixture/logs/$(date +%F)/shutdown.log" "Dry-run mode: no shutdown actions were executed" } test_shutdown_auto_order_and_scope() { local fixture fixture="$(new_fixture shutdown-auto)" local cmd_log="$fixture/commands.log" TEST_CMD_LOG="$cmd_log" \ TEST_STATE_DIR="$fixture/state" \ BARON_SSO_WORKTREE="$fixture/baron" \ TDC114_LOG_BASE="$fixture/logs" \ TDC114_ADB_CONNECT_ADDRESS=172.21.128.1:5562 \ DOCKER_BIN="$fixture/bin/docker" \ ADB_BIN="$fixture/bin/adb" \ PKILL_BIN="$fixture/bin/pkill" \ CHMOD_BIN="$fixture/bin/chmod" \ CHOWN_BIN="$fixture/bin/chown" \ bash "$ROOT_DIR/scripts/shutdown.sh" --auto assert_file_contains "$cmd_log" "pkill -f $ROOT_DIR/scripts/integration_tests.sh" assert_file_contains "$cmd_log" "pkill -f $ROOT_DIR/scripts/flutter-docker.sh" assert_file_contains "$cmd_log" "adb disconnect 172.21.128.1:5562" assert_file_contains "$cmd_log" "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml logs --no-color" assert_file_contains "$cmd_log" "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml down" assert_file_contains "$cmd_log" "docker rm -f old_baron_backend old_ory_kratos" assert_file_contains "$cmd_log" "chmod -R u+w config/.generated/" assert_file_contains "$cmd_log" "chown -R" local logs_line local down_line logs_line="$(grep -n "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml logs --no-color" "$cmd_log" | head -n1 | cut -d: -f1)" down_line="$(grep -n "docker compose -f docker-compose.yaml -f compose.infra.yaml -f compose.ory.yaml down" "$cmd_log" | head -n1 | cut -d: -f1)" if [ -z "$logs_line" ] || [ -z "$down_line" ] || [ "$logs_line" -ge "$down_line" ]; then fail "expected compose logs collection before compose down" fi } run_case "startup dry-run" test_startup_dry_run run_case "startup auto retry success" test_startup_auto_retry_success run_case "startup auto health failure tolerated" test_startup_auto_health_failure_tolerated run_case "startup login failure falls back" test_startup_login_failed_falls_back_to_base_smoke run_case "startup auto failure" test_startup_auto_failure run_case "wsl maintenance schedule" test_wsl_maintenance_schedule run_case "shutdown dry-run" test_shutdown_dry_run run_case "shutdown auto order and scope" test_shutdown_auto_order_and_scope say "PASS $PASS_COUNT case(s)"