#!/usr/bin/env bash # test-ci-triage-minimax-run.sh — Verify ci-triage-minimax-run against its spec. # # Strategy: CI_TRIAGE_FAKE_LOGS bypasses gac, CI_TRIAGE_FAKE_JSON / # CI_TRIAGE_FAKE_TRIAGE drive the sub-session paths, so exits 0/2/3, # redaction, and the failed_step handling are all exercised offline. set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" SCRIPT="$REPO_ROOT/scripts/ci-triage-minimax-run" GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' PASS=0 FAIL=0 assert_contains() { local label=$1 needle=$2 haystack=$3 if [[ "$haystack" == *"$needle"* ]]; then printf " ${GREEN}PASS${NC} %s\n" "$label"; PASS=$((PASS + 1)) else printf " ${RED}FAIL${NC} %s\n" "$label" printf " expected to contain: %s\n" "$needle"; FAIL=$((FAIL + 1)) fi } assert_not_contains() { local label=$1 needle=$2 haystack=$3 if [[ "$haystack" != *"$needle"* ]]; then printf " ${GREEN}PASS${NC} %s\n" "$label"; PASS=$((PASS + 1)) else printf " ${RED}FAIL${NC} %s\n" "$label" printf " expected NOT to contain: %s\n" "$needle"; FAIL=$((FAIL + 1)) fi } assert_eq() { local label=$1 expected=$2 actual=$3 if [[ "$expected" == "$actual" ]]; then printf " ${GREEN}PASS${NC} %s\n" "$label"; PASS=$((PASS + 1)) else printf " ${RED}FAIL${NC} %s\n" "$label" printf " expected: %s actual: %s\n" "$expected" "$actual"; FAIL=$((FAIL + 1)) fi } TMP=$(mktemp -d /tmp/test-ci-triage.XXXXXX) trap 'rm -rf "$TMP"' EXIT # gac-shaped NDJSON envelope + log lines, including a credential-shaped URL FAKE_LOGS="$TMP/logs.ndjson" cat > "$FAKE_LOGS" <<'EOF' {"job_id":1,"conclusion":"failure","failed_step":"Run tests","sliced":true,"note":"","markers":[],"lines":["cloning https://x-access-token:ghp_abcdEFGH1234@gitea.example/r.git","FAILED tests/test_x.py::test_y - AssertionError","exit code 1"]} EOF FAKE_LOGS_NOSTEP="$TMP/logs-nostep.ndjson" echo '{"job_id":1,"conclusion":"cancelled","failed_step":null,"sliced":false,"note":"","markers":[],"lines":["cancelled"]}' > "$FAKE_LOGS_NOSTEP" FAKE_OK_JSON="$TMP/ok.json" echo '{"is_error":false,"result":"done"}' > "$FAKE_OK_JSON" FAKE_ERR_JSON="$TMP/err.json" echo '{"is_error":true,"result":"boom"}' > "$FAKE_ERR_JSON" FAKE_TRIAGE="$TMP/triage.md" printf '## Failing step\nRun tests\n## Error excerpt\nFAILED tests/test_x.py\n## Probable cause\nAssertion failure in test_y.\n## Suggested next action\nRe-run that test.\n## Confidence\nhigh\n' > "$FAKE_TRIAGE" FAKE_TRIAGE_EMPTY_CAUSE="$TMP/triage-empty.md" printf '## Failing step\nRun tests\n## Probable cause\n## Confidence\nlow\n' > "$FAKE_TRIAGE_EMPTY_CAUSE" echo "== ci-triage-minimax-run: --help ==" HELP_OUT="$("$SCRIPT" --help 2>&1)" assert_contains "help shows usage" "--repo" "$HELP_OUT" echo "== usage errors (exit 1) ==" "$SCRIPT" >/dev/null 2>&1; assert_eq "no args exits 1" 1 $? "$SCRIPT" --repo badformat --run 1 >/dev/null 2>&1; assert_eq "bad repo format exits 1" 1 $? "$SCRIPT" --repo o/r --run abc >/dev/null 2>&1; assert_eq "non-integer run exits 1" 1 $? echo "== dryrun ==" DRY_OUT="$("$SCRIPT" --repo skynet/gac-scratch --run 42 --dryrun 2>&1)" assert_eq "dryrun exits 0" 0 $? assert_contains "dryrun shows gac command" "inspect skynet/gac-scratch --run 42" "$DRY_OUT" assert_contains "dryrun defaults to Flash" "MiniMax-M3.1-Flash-Preview" "$DRY_OUT" assert_contains "dryrun default effort low" "--effort low" "$DRY_OUT" assert_contains "dryrun defers fetch" "not fetched in dryrun" "$DRY_OUT" assert_contains "dryrun shows job passthrough default" "job: auto" "$DRY_OUT" echo "== dryrun --m3/--effort ==" M3_OUT="$("$SCRIPT" --repo skynet/gac-scratch --run 42 --m3 --dryrun 2>&1)" assert_eq "m3 dryrun exits 0" 0 $? assert_contains "m3 dryrun shows M3 model" "MiniMax-M3[1m]" "$M3_OUT" assert_not_contains "m3 dryrun sends no effort" "--effort" "$M3_OUT" EF_OUT="$("$SCRIPT" --repo skynet/gac-scratch --run 42 --effort high --dryrun 2>&1)" assert_contains "explicit effort overrides default" "--effort high" "$EF_OUT" "$SCRIPT" --repo o/r --run 1 --effort bogus --dryrun >/dev/null 2>&1; assert_eq "bad effort exits 1" 1 $? echo "== fake logs: redaction + failing step in prompt, exit 0 ==" OUT0="$TMP/out0.md" RUN_OUT="$(CI_TRIAGE_FAKE_LOGS="$FAKE_LOGS" CI_TRIAGE_FAKE_JSON="$FAKE_OK_JSON" CI_TRIAGE_FAKE_TRIAGE="$FAKE_TRIAGE" \ "$SCRIPT" --repo o/r --run 7 --out "$OUT0" 2>&1)" assert_eq "happy path exits 0" 0 $? assert_contains "triage file written" "ok: triage written" "$RUN_OUT" echo "== fake logs: null failed_step still succeeds ==" CI_TRIAGE_FAKE_LOGS="$FAKE_LOGS_NOSTEP" CI_TRIAGE_FAKE_JSON="$FAKE_OK_JSON" CI_TRIAGE_FAKE_TRIAGE="$FAKE_TRIAGE" \ "$SCRIPT" --repo o/r --run 8 --out "$TMP/out-nostep.md" >/dev/null 2>&1 assert_eq "null failed_step exits 0" 0 $? echo "== gate: empty probable cause -> exit 3 ==" CI_TRIAGE_FAKE_LOGS="$FAKE_LOGS" CI_TRIAGE_FAKE_JSON="$FAKE_OK_JSON" CI_TRIAGE_FAKE_TRIAGE="$FAKE_TRIAGE_EMPTY_CAUSE" \ "$SCRIPT" --repo o/r --run 9 --out "$TMP/out3.md" >/dev/null 2>&1 assert_eq "empty probable-cause exits 3" 3 $? echo "== gate: no triage file -> exit 3 ==" CI_TRIAGE_FAKE_LOGS="$FAKE_LOGS" CI_TRIAGE_FAKE_JSON="$FAKE_OK_JSON" \ "$SCRIPT" --repo o/r --run 10 --out "$TMP/out3b.md" >/dev/null 2>&1 assert_eq "missing triage file exits 3" 3 $? echo "== is_error envelope -> exit 2 ==" CI_TRIAGE_FAKE_LOGS="$FAKE_LOGS" CI_TRIAGE_FAKE_JSON="$FAKE_ERR_JSON" \ "$SCRIPT" --repo o/r --run 11 --out "$TMP/out2.md" >/dev/null 2>&1 assert_eq "is_error envelope exits 2" 2 $? echo "" echo "ci-triage-minimax-run: $PASS passed, $FAIL failed" [[ "$FAIL" -eq 0 ]]