#!/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}"