doatlas-2 / scripts /test-gate.sh
Iostream-Li's picture
Add files using upload-large-folder tool
9c12e58 verified
#!/usr/bin/env bash
# Unified test gate for the DoAtlas monorepo.
#
# Pipeline:
# 1) api-server unit + integration suite
# 2) research-engine pytest
# 3) doatlas-web mocked Playwright (--project=chromium)
# 4) provider smoke check (informational; auto-skipped when no
# provider credentials are present in the env)
# 5) e2e-live Playwright (skipped unless E2E_LIVE=1)
#
# Flags / env:
# --coverage Run steps 1-2 under c8 / pytest-cov. Emits:
# coverage/html/api-server/index.html
# coverage/html/research-engine/index.html
# coverage/lcov.info (merged)
# CONTINUE_ON_ERROR=1 Run all steps even if earlier ones fail and
# report a final summary. Default (unset/0) is
# fail-fast at the first hard failure.
# E2E_LIVE=1 Include the live-stack Playwright project.
# SKIP_SMOKE=1 Skip the provider smoke check entirely.
#
# Usage:
# pnpm run test:gate
# pnpm run test:gate -- --coverage
# E2E_LIVE=1 pnpm run test:gate
# CONTINUE_ON_ERROR=1 pnpm run test:gate
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${REPO_ROOT}"
COVERAGE=0
for arg in "$@"; do
case "$arg" in
--coverage) COVERAGE=1 ;;
--help|-h)
sed -n '2,28p' "$0"; exit 0 ;;
*) echo "test-gate: unknown flag '$arg'" >&2; exit 64 ;;
esac
done
CONTINUE_ON_ERROR="${CONTINUE_ON_ERROR:-0}"
E2E_LIVE="${E2E_LIVE:-0}"
SKIP_SMOKE="${SKIP_SMOKE:-0}"
# --- DATABASE_URL gate -------------------------------------------------
#
# Several api-server suites (auth-flow, research-bridge, shadow-budget,
# shadow-sampling-load — see Task #190 / #197) self-skip when
# DATABASE_URL is unset so they stay no-ops on dev machines without a
# local Postgres. That self-skip is fine for a casual `pnpm test` on a
# laptop, but it is **not** acceptable in the gate: if CI ran without a
# DSN, the load coverage would silently disappear and a regression in
# the shadow-sampling fire-and-forget path could land on main without
# tripping any alarm.
#
# Fail loudly here so that future contributors who refactor the gate
# (or move the suite into a different CI runner) immediately notice
# the missing DSN instead of getting a green run with skipped tests.
# See docs/TESTING.md ("CI requirements") for the rationale.
if [[ -z "${DATABASE_URL:-}" ]]; then
echo "test-gate: DATABASE_URL is not set." >&2
echo "test-gate: The gate requires a Postgres DSN so the api-server" >&2
echo "test-gate: integration suites (including the shadow-sampling" >&2
echo "test-gate: load test from Task #190) actually run instead of" >&2
echo "test-gate: self-skipping. See docs/TESTING.md for details." >&2
exit 65
fi
# Mark this run as the gate so downstream tests can promote their
# self-skips into hard failures (Task #197). This pairs with the check
# above: if a test still finds DATABASE_URL unset *and* TEST_GATE=1, it
# means someone stripped the DSN between here and the test runner, and
# the test should fail rather than silently skip.
export TEST_GATE=1
declare -a STEP_NAMES
declare -a STEP_RESULTS
OVERALL_RC=0
step() {
echo
echo "================================================================"
echo " $1"
echo "================================================================"
}
run_step() {
local label="$1"; shift
local gating="$1"; shift # "gate" | "info"
step "${label}"
if "$@"; then
STEP_NAMES+=("${label}")
STEP_RESULTS+=("PASS")
return 0
fi
local rc=$?
if [[ "${gating}" == "info" ]]; then
echo "test-gate: ${label} reported failures (informational, not gating; rc=${rc})" >&2
STEP_NAMES+=("${label}")
STEP_RESULTS+=("WARN")
return 0
fi
STEP_NAMES+=("${label}")
STEP_RESULTS+=("FAIL(rc=${rc})")
OVERALL_RC=1
if [[ "${CONTINUE_ON_ERROR}" != "1" ]]; then
print_summary
echo "test-gate: FAIL (fail-fast on '${label}'; set CONTINUE_ON_ERROR=1 to keep going)"
exit "${rc}"
fi
return 0
}
print_summary() {
echo
echo "================================================================"
echo " test-gate summary"
echo "================================================================"
local i
for i in "${!STEP_NAMES[@]}"; do
printf " %-7s %s\n" "${STEP_RESULTS[$i]}" "${STEP_NAMES[$i]}"
done
}
# --- 1/5 api-server ----------------------------------------------------
if [[ "${COVERAGE}" == "1" ]]; then
run_step "1/5 api-server unit + integration tests (coverage)" "gate" \
pnpm --filter @workspace/api-server run test:coverage
else
run_step "1/5 api-server unit + integration tests" "gate" \
pnpm --filter @workspace/api-server run test
fi
# --- 2/5 research-engine ----------------------------------------------
if [[ "${COVERAGE}" == "1" ]]; then
run_step "2/5 research-engine pytest (coverage)" "gate" \
pnpm --filter @workspace/research-engine run test:coverage
else
run_step "2/5 research-engine pytest" "gate" \
pnpm --filter @workspace/research-engine run test
fi
# --- 3/5 mocked Playwright --------------------------------------------
run_step "3/5 doatlas-web mocked Playwright (chromium)" "gate" \
pnpm --filter @workspace/doatlas-web exec playwright test --project=chromium
# --- 3.5/5 doatlas-web component tests (vitest) -----------------------
run_step "0/5 quarantine audit (Task #254 B0)" "gate" \
pnpm --filter @workspace/scripts run audit:quarantine
run_step "3.5/5 doatlas-web component tests (vitest)" "gate" \
pnpm --filter @workspace/doatlas-web run test
# --- 4/5 provider smoke (informational, credential-aware) -------------
have_any_provider_key() {
[[ -n "${ANTHROPIC_API_KEY:-}" ]] && return 0
[[ -n "${OPENAI_API_KEY:-}" ]] && return 0
[[ -n "${MINIMAX_API_KEY:-}" ]] && return 0
[[ -n "${MODELSCOPE_API_TOKEN:-}" ]] && return 0
[[ -n "${OPENROUTER_API_KEY:-}" ]] && return 0
return 1
}
if [[ "${SKIP_SMOKE}" == "1" ]]; then
step "4/5 provider smoke check SKIPPED (SKIP_SMOKE=1)"
STEP_NAMES+=("4/5 provider smoke check"); STEP_RESULTS+=("SKIP")
elif ! have_any_provider_key; then
step "4/5 provider smoke check SKIPPED (no provider credentials in env)"
STEP_NAMES+=("4/5 provider smoke check"); STEP_RESULTS+=("SKIP")
else
run_step "4/5 provider smoke check (informational)" "info" \
pnpm run smoke:providers
fi
# --- 5/5 e2e-live -----------------------------------------------------
if [[ "${E2E_LIVE}" == "1" ]]; then
run_step "5/5 e2e-live Playwright (live stack)" "gate" \
pnpm --filter @workspace/doatlas-web exec playwright test \
--config=playwright.live.config.ts
else
step "5/5 e2e-live SKIPPED (set E2E_LIVE=1 to enable)"
STEP_NAMES+=("5/5 e2e-live"); STEP_RESULTS+=("SKIP")
fi
# --- coverage post-processing -----------------------------------------
if [[ "${COVERAGE}" == "1" ]]; then
step "coverage: merging lcov reports"
mkdir -p coverage
: > coverage/lcov.info
if [[ -f coverage/html/api-server/lcov.info ]]; then
cat coverage/html/api-server/lcov.info >> coverage/lcov.info
fi
if [[ -f coverage/lcov-research-engine.info ]]; then
cat coverage/lcov-research-engine.info >> coverage/lcov.info
fi
echo " -> coverage/lcov.info ($(wc -l < coverage/lcov.info) lines)"
echo " -> coverage/html/api-server/index.html"
echo " -> coverage/html/research-engine/index.html"
fi
print_summary
if [[ "${OVERALL_RC}" -eq 0 ]]; then
echo
echo "test-gate: PASS"
else
echo
echo "test-gate: FAIL (one or more gating steps failed; see summary)"
fi
exit "${OVERALL_RC}"