feat(mm-worktree-task): run from any planner-backed repo — cp_cli dir + context resolution (--context, MM_WORKTREE_TASK_CP_*), empty-docs guard, tests + spec
cp_cli lives in agent-runtimes; previously every planner call ran 'python3 -m lib.cp_cli' from the current repo with no --context, so the script only worked inside agent-runtimes. The context now resolves from ~/.cpconfig by matching project_id to the repo directory name. Verified live from hatchmate (item + parent + documents fetched via the hatchmate context). Claude-Session: https://claude.ai/code/session_01JgJbWzVJioVznVTFyQFEfx
This commit is contained in:
268
tests/test-bg-model-call.sh
Executable file
268
tests/test-bg-model-call.sh
Executable file
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-bg-model-call.sh — Verify bg-model-call dryrun behaviour against the spec.
|
||||
#
|
||||
# Strategy: all assertions use --dryrun (-n), so no CP calls are made and
|
||||
# no output files are written. Prompt files are created in a tempdir.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BG_MODEL_CALL="$REPO_ROOT/scripts/bg-model-call"
|
||||
|
||||
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"
|
||||
printf " actual output:\n"
|
||||
printf '%s\n' "$haystack" | sed 's/^/ /'
|
||||
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_exit_code() {
|
||||
local label=$1 expected=$2 actual=$3
|
||||
if [[ "$expected" == "$actual" ]]; then
|
||||
printf " ${GREEN}PASS${NC} %s (exit=%s)\n" "$label" "$actual"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
printf " ${RED}FAIL${NC} %s (expected exit=%s, got exit=%s)\n" "$label" "$expected" "$actual"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# Create a temp directory for prompt and output files
|
||||
TMPDIR_TEST="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMPDIR_TEST"' EXIT
|
||||
|
||||
PROMPT_FILE="${TMPDIR_TEST}/prompt.txt"
|
||||
OUTPUT_FILE="${TMPDIR_TEST}/result.txt"
|
||||
echo "This is a test prompt for the spec review." > "$PROMPT_FILE"
|
||||
|
||||
run() {
|
||||
"$BG_MODEL_CALL" "$@" 2>&1
|
||||
}
|
||||
|
||||
# === Pre-flight ===
|
||||
if [[ ! -x "$BG_MODEL_CALL" ]]; then
|
||||
echo "bg-model-call not found or not executable at $BG_MODEL_CALL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# === Test 1: --help ===
|
||||
echo "Test 1: --help"
|
||||
out=$(run --help)
|
||||
rc=$?
|
||||
assert_exit_code "--help exits 0" 0 "$rc"
|
||||
assert_contains "--help shows script name" "bg-model-call" "$out"
|
||||
assert_contains "--help shows --model" "--model" "$out"
|
||||
assert_contains "--help shows --prompt" "--prompt" "$out"
|
||||
assert_contains "--help shows --output" "--output" "$out"
|
||||
assert_contains "--help shows --dryrun" "--dryrun" "$out"
|
||||
assert_contains "--help shows --template" "--template" "$out"
|
||||
|
||||
# === Test 2: basic minimax dryrun ===
|
||||
echo
|
||||
echo "Test 2: minimax dryrun"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "minimax dryrun exits 0" 0 "$rc"
|
||||
assert_contains "shows dryrun header" "[dryrun]" "$out"
|
||||
assert_contains "shows model" "minimax" "$out"
|
||||
assert_contains "shows resolved template" "minimax-planning" "$out"
|
||||
assert_contains "shows CP URL default" "http://localhost:8100" "$out"
|
||||
assert_contains "shows project ID default" "bg-model-call" "$out"
|
||||
assert_contains "shows task name" "bg-model-call-minimax" "$out"
|
||||
assert_contains "shows prompt file path" "$PROMPT_FILE" "$out"
|
||||
assert_contains "shows output file path" "$OUTPUT_FILE" "$out"
|
||||
assert_contains "shows sentinel path" "${OUTPUT_FILE}.done" "$out"
|
||||
assert_contains "shows prompt size" "bytes" "$out"
|
||||
assert_not_contains "no CP calls in dryrun" "Submitting task" "$out"
|
||||
|
||||
# === Test 3: sonnet dryrun — template mapping ===
|
||||
echo
|
||||
echo "Test 3: sonnet dryrun — template mapping"
|
||||
out=$(run --dryrun --model sonnet --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "sonnet dryrun exits 0" 0 "$rc"
|
||||
assert_contains "sonnet maps to sonnet-code-repo" "sonnet-code-repo" "$out"
|
||||
assert_contains "sonnet task name correct" "bg-model-call-sonnet" "$out"
|
||||
|
||||
# === Test 4: haiku dryrun — template mapping ===
|
||||
echo
|
||||
echo "Test 4: haiku dryrun — template mapping"
|
||||
out=$(run --dryrun --model haiku --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "haiku dryrun exits 0" 0 "$rc"
|
||||
assert_contains "haiku maps to haiku-code-repo" "haiku-code-repo" "$out"
|
||||
|
||||
# === Test 5: opus dryrun — template mapping ===
|
||||
echo
|
||||
echo "Test 5: opus dryrun — template mapping"
|
||||
out=$(run --dryrun --model opus --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "opus dryrun exits 0" 0 "$rc"
|
||||
assert_contains "opus maps to opus-code-repo" "opus-code-repo" "$out"
|
||||
|
||||
# === Test 6: --template override ===
|
||||
echo
|
||||
echo "Test 6: --template override"
|
||||
out=$(run --dryrun --template minimax-planning --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "--template override exits 0" 0 "$rc"
|
||||
assert_contains "template override shown" "minimax-planning" "$out"
|
||||
# No model line since only --template was given, not --model
|
||||
assert_not_contains "no model line when using --template only" "Model:" "$out"
|
||||
|
||||
# === Test 7: custom --project and --task-name ===
|
||||
echo
|
||||
echo "Test 7: custom project and task name"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE" \
|
||||
--project my-project --task-name review-task)
|
||||
rc=$?
|
||||
assert_exit_code "custom project/task exits 0" 0 "$rc"
|
||||
assert_contains "custom project shown" "my-project" "$out"
|
||||
assert_contains "custom task name shown" "review-task" "$out"
|
||||
|
||||
# === Test 8: custom --url ===
|
||||
echo
|
||||
echo "Test 8: custom --url"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE" \
|
||||
--url "http://10.111.1.11:31800")
|
||||
rc=$?
|
||||
assert_exit_code "custom url exits 0" 0 "$rc"
|
||||
assert_contains "custom URL shown" "http://10.111.1.11:31800" "$out"
|
||||
|
||||
# === Test 9: CP_URL env var ===
|
||||
echo
|
||||
echo "Test 9: CP_URL env var"
|
||||
out=$(CP_URL="http://my-cp:9000" run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "CP_URL env var exits 0" 0 "$rc"
|
||||
assert_contains "CP_URL env var used" "http://my-cp:9000" "$out"
|
||||
|
||||
# === Test 10: -n shorthand ===
|
||||
echo
|
||||
echo "Test 10: -n shorthand for dryrun"
|
||||
out=$(run -n --model sonnet --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "-n shorthand exits 0" 0 "$rc"
|
||||
assert_contains "-n shows dryrun header" "[dryrun]" "$out"
|
||||
|
||||
# === Test 11: error — missing --model and --template ===
|
||||
echo
|
||||
echo "Test 11: error — missing --model and --template"
|
||||
out=$(run --dryrun --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "missing model exits 1" 1 "$rc"
|
||||
assert_contains "missing model error message" "--model is required" "$out"
|
||||
|
||||
# === Test 12: error — missing --prompt ===
|
||||
echo
|
||||
echo "Test 12: error — missing --prompt"
|
||||
out=$(run --dryrun --model minimax --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "missing prompt exits 1" 1 "$rc"
|
||||
assert_contains "missing prompt error" "--prompt is required" "$out"
|
||||
|
||||
# === Test 13: error — missing --output ===
|
||||
echo
|
||||
echo "Test 13: error — missing --output"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "missing output exits 1" 1 "$rc"
|
||||
assert_contains "missing output error" "--output is required" "$out"
|
||||
|
||||
# === Test 14: error — unknown model alias ===
|
||||
echo
|
||||
echo "Test 14: error — unknown model alias"
|
||||
out=$(run --dryrun --model bogusmodel --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "unknown model exits 1" 1 "$rc"
|
||||
assert_contains "unknown model error" "Unknown model alias" "$out"
|
||||
assert_contains "unknown model error lists valid values" "minimax" "$out"
|
||||
|
||||
# === Test 15: error — prompt file not found ===
|
||||
echo
|
||||
echo "Test 15: error — prompt file not found"
|
||||
out=$(run --dryrun --model minimax --prompt "/tmp/definitely-does-not-exist-99999.txt" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "missing prompt file exits 1" 1 "$rc"
|
||||
assert_contains "missing prompt file error" "Cannot read prompt file" "$out"
|
||||
|
||||
# === Test 16: error — output directory does not exist ===
|
||||
echo
|
||||
echo "Test 16: error — output directory does not exist"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "/nonexistent/dir/result.txt")
|
||||
rc=$?
|
||||
assert_exit_code "missing output dir exits 1" 1 "$rc"
|
||||
assert_contains "missing output dir error" "Output directory does not exist" "$out"
|
||||
|
||||
# === Test 17: error — invalid timeout ===
|
||||
echo
|
||||
echo "Test 17: error — invalid --timeout"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE" --timeout abc)
|
||||
rc=$?
|
||||
assert_exit_code "invalid timeout exits 1" 1 "$rc"
|
||||
assert_contains "invalid timeout error" "--timeout must be a positive integer" "$out"
|
||||
|
||||
# === Test 18: error — invalid poll-interval ===
|
||||
echo
|
||||
echo "Test 18: error — invalid --poll-interval"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE" --poll-interval 0)
|
||||
rc=$?
|
||||
assert_exit_code "invalid poll-interval exits 1" 1 "$rc"
|
||||
assert_contains "invalid poll-interval error" "--poll-interval must be a positive integer" "$out"
|
||||
|
||||
# === Test 19: custom timeout and poll-interval shown in dryrun ===
|
||||
echo
|
||||
echo "Test 19: custom timeout and poll-interval in dryrun"
|
||||
out=$(run --dryrun --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE" \
|
||||
--timeout 3600 --poll-interval 30)
|
||||
rc=$?
|
||||
assert_exit_code "custom timeout/poll exits 0" 0 "$rc"
|
||||
assert_contains "custom timeout shown" "3600s" "$out"
|
||||
assert_contains "custom poll shown" "30s" "$out"
|
||||
|
||||
# === Test 20: unknown flag ===
|
||||
echo
|
||||
echo "Test 20: unknown flag"
|
||||
out=$(run --dryrun --bogus-flag --model minimax --prompt "$PROMPT_FILE" --output "$OUTPUT_FILE")
|
||||
rc=$?
|
||||
assert_exit_code "unknown flag exits 1" 1 "$rc"
|
||||
assert_contains "unknown flag error" "Unknown flag" "$out"
|
||||
|
||||
# === Summary ===
|
||||
echo
|
||||
echo "================================"
|
||||
TOTAL=$((PASS + FAIL))
|
||||
if [[ "$FAIL" -eq 0 ]]; then
|
||||
printf "${GREEN}All %d tests passed${NC}\n" "$TOTAL"
|
||||
exit 0
|
||||
else
|
||||
printf "${RED}%d of %d tests failed${NC}\n" "$FAIL" "$TOTAL"
|
||||
exit 1
|
||||
fi
|
||||
@@ -116,6 +116,29 @@ assert_contains "focus line present" "Only touch CI files" "$DRY_FOCUS_OUT"
|
||||
|
||||
echo
|
||||
echo "== mm-worktree-task: --collect with no worktree errors =="
|
||||
# --- context resolution (spec Behaviour §0) ---
|
||||
FAKE_CPCONFIG="$(mktemp)"
|
||||
cat > "$FAKE_CPCONFIG" <<'YAML'
|
||||
current_context: other
|
||||
contexts:
|
||||
- name: other
|
||||
project_id: something-else
|
||||
- name: reponame-ctx
|
||||
project_id: REPO_NAME_PLACEHOLDER
|
||||
YAML
|
||||
sed -i "s/REPO_NAME_PLACEHOLDER/$(basename "$(git rev-parse --show-toplevel)")/" "$FAKE_CPCONFIG"
|
||||
CTX_OUT="$(MM_WORKTREE_TASK_CPCONFIG="$FAKE_CPCONFIG" "$SCRIPT" "$TEST_UUID" --slug testslug --dryrun 2>&1)"
|
||||
assert_contains "context auto-resolves from cpconfig by repo name" "cp-cli ctx: reponame-ctx" "$CTX_OUT"
|
||||
CTX_FLAG_OUT="$(MM_WORKTREE_TASK_CPCONFIG="$FAKE_CPCONFIG" "$SCRIPT" "$TEST_UUID" --slug testslug --dryrun --context explicit 2>&1)"
|
||||
assert_contains "--context overrides cpconfig resolution" "cp-cli ctx: explicit" "$CTX_FLAG_OUT"
|
||||
CTX_ENV_OUT="$(MM_WORKTREE_TASK_CPCONFIG="$FAKE_CPCONFIG" MM_WORKTREE_TASK_CP_CONTEXT=fromenv "$SCRIPT" "$TEST_UUID" --slug testslug --dryrun 2>&1)"
|
||||
assert_contains "MM_WORKTREE_TASK_CP_CONTEXT overrides cpconfig resolution" "cp-cli ctx: fromenv" "$CTX_ENV_OUT"
|
||||
CTX_NONE_OUT="$(MM_WORKTREE_TASK_CPCONFIG=/nonexistent "$SCRIPT" "$TEST_UUID" --slug testslug --dryrun 2>&1)"
|
||||
assert_contains "no matching context falls back to cp-cli default" "cp-cli ctx: <cp-cli default>" "$CTX_NONE_OUT"
|
||||
CLI_DIR_OUT="$(MM_WORKTREE_TASK_CP_CLI_DIR=/nonexistent "$SCRIPT" "$TEST_UUID" --slug testslug --dryrun 2>&1)"
|
||||
assert_contains "cp-cli dir resolution is reported" "cp-cli from:" "$CLI_DIR_OUT"
|
||||
rm -f "$FAKE_CPCONFIG"
|
||||
|
||||
unset MM_WORKTREE_TASK_FAKE_ITEM
|
||||
unset MM_WORKTREE_TASK_FAKE_DOCS_DIR
|
||||
COLLECT_OUT="$("$SCRIPT" "$TEST_UUID" --slug does-not-exist-slug --collect 2>&1)"
|
||||
|
||||
310
tests/test-wait-for-version.sh
Executable file
310
tests/test-wait-for-version.sh
Executable file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
WAIT="$SCRIPT_DIR/scripts/wait-for-version"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m'
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
|
||||
assert_eq() {
|
||||
local desc="$1" expected="$2" actual="$3"
|
||||
if [[ "$expected" == "$actual" ]]; then
|
||||
echo -e "${GREEN}PASS${NC}: $desc"
|
||||
((pass++)) || true
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: $desc"
|
||||
echo " expected: $expected"
|
||||
echo " actual: $actual"
|
||||
((fail++)) || true
|
||||
fi
|
||||
}
|
||||
|
||||
assert_match() {
|
||||
local desc="$1" pattern="$2" actual="$3"
|
||||
if [[ "$actual" =~ $pattern ]]; then
|
||||
echo -e "${GREEN}PASS${NC}: $desc"
|
||||
((pass++)) || true
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: $desc"
|
||||
echo " pattern: $pattern"
|
||||
echo " actual: $actual"
|
||||
((fail++)) || true
|
||||
fi
|
||||
}
|
||||
|
||||
assert_exit() {
|
||||
local desc="$1" expected="$2" actual="$3"
|
||||
if [[ "$expected" -eq "$actual" ]]; then
|
||||
echo -e "${GREEN}PASS${NC}: $desc"
|
||||
((pass++)) || true
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: $desc"
|
||||
echo " expected exit: $expected"
|
||||
echo " actual exit: $actual"
|
||||
((fail++)) || true
|
||||
fi
|
||||
}
|
||||
|
||||
assert_between() {
|
||||
local desc="$1" lo="$2" hi="$3" val="$4"
|
||||
if (( val >= lo && val <= hi )); then
|
||||
echo -e "${GREEN}PASS${NC}: $desc (${val} in [${lo},${hi}])"
|
||||
((pass++)) || true
|
||||
else
|
||||
echo -e "${RED}FAIL${NC}: $desc (${val} not in [${lo},${hi}])"
|
||||
((fail++)) || true
|
||||
fi
|
||||
}
|
||||
|
||||
# --- test env setup ---
|
||||
|
||||
tmp_root="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_root"' EXIT
|
||||
|
||||
MOCK_DIR="$tmp_root/bin"
|
||||
mkdir -p "$MOCK_DIR"
|
||||
|
||||
# URL-aware mock curl: routes to different fixture files based on URL path.
|
||||
# Environment variables:
|
||||
# MOCK_BRANCH_BODY / MOCK_BRANCH_CODE — for /branches/main
|
||||
# MOCK_RUNS_BODY / MOCK_RUNS_CODE — for /actions/runs
|
||||
# MOCK_RELEASE_BODY / MOCK_RELEASE_CODE — for /releases/latest
|
||||
# MOCK_GLOBAL_CODE — override all codes (for auth/404 tests)
|
||||
# MOCK_CURL_FAIL — make curl exit non-zero
|
||||
cat > "$MOCK_DIR/curl" <<'MOCK'
|
||||
#!/usr/bin/env bash
|
||||
url=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
http://*|https://*) url="$1"; shift ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "${MOCK_CURL_FAIL:-}" ]] && exit 7
|
||||
if [[ -n "${MOCK_GLOBAL_CODE:-}" ]]; then
|
||||
printf '\n%s' "$MOCK_GLOBAL_CODE"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$url" == */branches/main ]]; then
|
||||
[[ -n "${MOCK_BRANCH_BODY:-}" && -f "${MOCK_BRANCH_BODY}" ]] && cat "$MOCK_BRANCH_BODY"
|
||||
printf '\n%s' "${MOCK_BRANCH_CODE:-200}"
|
||||
elif [[ "$url" == */actions/runs* ]]; then
|
||||
[[ -n "${MOCK_RUNS_BODY:-}" && -f "${MOCK_RUNS_BODY}" ]] && cat "$MOCK_RUNS_BODY"
|
||||
printf '\n%s' "${MOCK_RUNS_CODE:-200}"
|
||||
elif [[ "$url" == */releases/latest ]]; then
|
||||
[[ -n "${MOCK_RELEASE_BODY:-}" && -f "${MOCK_RELEASE_BODY}" ]] && cat "$MOCK_RELEASE_BODY"
|
||||
printf '\n%s' "${MOCK_RELEASE_CODE:-200}"
|
||||
fi
|
||||
MOCK
|
||||
chmod +x "$MOCK_DIR/curl"
|
||||
|
||||
run_with_mock() {
|
||||
PATH="$MOCK_DIR:$PATH" "$WAIT" "$@"
|
||||
}
|
||||
|
||||
# --- Fixtures ---
|
||||
|
||||
FIX_BRANCH="$tmp_root/branch.json"
|
||||
cat > "$FIX_BRANCH" <<'JSON'
|
||||
{"name":"main","commit":{"id":"abc1234567890def1234567890def1234567890de"}}
|
||||
JSON
|
||||
|
||||
FIX_RUNS_SUCCESS="$tmp_root/runs_success.json"
|
||||
cat > "$FIX_RUNS_SUCCESS" <<'JSON'
|
||||
{"workflow_runs":[
|
||||
{"id":42,"name":"version","status":"completed","conclusion":"success"},
|
||||
{"id":41,"name":"test", "status":"completed","conclusion":"success"}
|
||||
]}
|
||||
JSON
|
||||
|
||||
FIX_RUNS_FAILED="$tmp_root/runs_failed.json"
|
||||
cat > "$FIX_RUNS_FAILED" <<'JSON'
|
||||
{"workflow_runs":[
|
||||
{"id":42,"name":"version","status":"completed","conclusion":"failure"}
|
||||
]}
|
||||
JSON
|
||||
|
||||
FIX_RUNS_INPROGRESS="$tmp_root/runs_inprogress.json"
|
||||
cat > "$FIX_RUNS_INPROGRESS" <<'JSON'
|
||||
{"workflow_runs":[
|
||||
{"id":42,"name":"version","status":"in_progress","conclusion":null}
|
||||
]}
|
||||
JSON
|
||||
|
||||
FIX_RUNS_EMPTY="$tmp_root/runs_empty.json"
|
||||
cat > "$FIX_RUNS_EMPTY" <<'JSON'
|
||||
{"workflow_runs":[]}
|
||||
JSON
|
||||
|
||||
FIX_RUNS_WRONG_WORKFLOW="$tmp_root/runs_wrong.json"
|
||||
cat > "$FIX_RUNS_WRONG_WORKFLOW" <<'JSON'
|
||||
{"workflow_runs":[
|
||||
{"id":42,"name":"test","status":"completed","conclusion":"success"}
|
||||
]}
|
||||
JSON
|
||||
|
||||
FIX_RELEASE="$tmp_root/release.json"
|
||||
cat > "$FIX_RELEASE" <<'JSON'
|
||||
{"id":10,"tag_name":"v1.2.3.45","name":"v1.2.3.45"}
|
||||
JSON
|
||||
|
||||
echo "=== wait-for-version tests ==="
|
||||
echo
|
||||
|
||||
# --- Help ---
|
||||
out=$("$WAIT" --help 2>&1); rc=$?
|
||||
assert_exit "help exits 0" 0 "$rc"
|
||||
assert_match "help mentions REPO" "REPO" "$out"
|
||||
assert_match "help lists exit codes" "Exit codes" "$out"
|
||||
assert_match "help mentions --workflow" "\-\-workflow" "$out"
|
||||
|
||||
# --- Usage errors ---
|
||||
out=$("$WAIT" 2>&1); rc=$?
|
||||
assert_exit "no args exits 2" 2 "$rc"
|
||||
assert_match "no args error" "REPO is required" "$out"
|
||||
|
||||
out=$("$WAIT" noslash 2>&1); rc=$?
|
||||
assert_exit "REPO without slash exits 2" 2 "$rc"
|
||||
assert_match "REPO slash error" "owner/name" "$out"
|
||||
|
||||
out=$("$WAIT" --timeout abc owner/repo 2>&1); rc=$?
|
||||
assert_exit "non-numeric timeout exits 2" 2 "$rc"
|
||||
|
||||
out=$("$WAIT" --interval 0 owner/repo 2>&1); rc=$?
|
||||
assert_exit "interval 0 exits 2" 2 "$rc"
|
||||
|
||||
out=$("$WAIT" --unknown-flag owner/repo 2>&1); rc=$?
|
||||
assert_exit "unknown flag exits 2" 2 "$rc"
|
||||
|
||||
out=$("$WAIT" owner/repo extra 2>&1); rc=$?
|
||||
assert_exit "extra positional arg exits 2" 2 "$rc"
|
||||
|
||||
# --- Dryrun ---
|
||||
out=$(env -u GITEA_TOKEN -u GITHUB_TOKEN "$WAIT" --dryrun myorg/myrepo 2>&1); rc=$?
|
||||
assert_exit "dryrun exits 0" 0 "$rc"
|
||||
assert_match "dryrun shows server" "Server: " "$out"
|
||||
assert_match "dryrun shows repo" "Repo: myorg/myrepo" "$out"
|
||||
assert_match "dryrun shows workflow" "Workflow: version" "$out"
|
||||
assert_match "dryrun shows timeout" "Timeout: 300s" "$out"
|
||||
assert_match "dryrun shows interval" "Interval: 15s" "$out"
|
||||
assert_match "dryrun shows Auth: no" "Auth: no" "$out"
|
||||
assert_match "dryrun shows Actions API" "api/v1/repos/myorg/myrepo/actions/runs" "$out"
|
||||
|
||||
# Auth: yes via --token
|
||||
out=$(env -u GITEA_TOKEN -u GITHUB_TOKEN "$WAIT" --dryrun -t tok123 myorg/myrepo 2>&1)
|
||||
assert_match "--token -> Auth: yes" "Auth: yes" "$out"
|
||||
|
||||
# Auth: yes via GITEA_TOKEN env
|
||||
out=$(env -u GITHUB_TOKEN GITEA_TOKEN=xyz "$WAIT" --dryrun myorg/myrepo 2>&1)
|
||||
assert_match "GITEA_TOKEN -> Auth: yes" "Auth: yes" "$out"
|
||||
|
||||
# Custom --server appears in Actions API URL
|
||||
out=$("$WAIT" --dryrun --server https://example.test myorg/myrepo 2>&1)
|
||||
assert_match "custom --server in Actions API" "https://example\.test/api/v1/repos/myorg/myrepo/actions/runs" "$out"
|
||||
|
||||
# Custom --workflow reflected in dryrun
|
||||
out=$("$WAIT" --dryrun --workflow release myorg/myrepo 2>&1)
|
||||
assert_match "custom --workflow shown" "Workflow: release" "$out"
|
||||
|
||||
# --- Successful flow via mock ---
|
||||
export MOCK_BRANCH_BODY="$FIX_BRANCH"
|
||||
export MOCK_RUNS_BODY="$FIX_RUNS_SUCCESS"
|
||||
export MOCK_RELEASE_BODY="$FIX_RELEASE"
|
||||
unset MOCK_GLOBAL_CODE MOCK_CURL_FAIL MOCK_BRANCH_CODE MOCK_RUNS_CODE MOCK_RELEASE_CODE
|
||||
|
||||
out=$(run_with_mock --timeout 5 --interval 1 test/repo 2>&1)
|
||||
rc=$?
|
||||
# stdout is the last line (tag); stderr has the "Waiting..." message
|
||||
tag=$(run_with_mock --timeout 5 --interval 1 test/repo 2>/dev/null)
|
||||
assert_exit "success exits 0" 0 "$rc"
|
||||
assert_eq "success prints tag" "v1.2.3.45" "$tag"
|
||||
|
||||
# Only the version workflow is selected, not the test workflow
|
||||
out=$(run_with_mock --timeout 5 --interval 1 test/repo 2>/dev/null)
|
||||
assert_eq "selects correct workflow by name" "v1.2.3.45" "$out"
|
||||
|
||||
# -- Verbose output to stderr ---
|
||||
stderr_out=$(run_with_mock --verbose --timeout 5 --interval 1 test/repo 2>&1 >/dev/null)
|
||||
assert_match "verbose mentions Waiting" "Waiting for" "$stderr_out"
|
||||
|
||||
# --- CI failure -> exit 1 ---
|
||||
export MOCK_RUNS_BODY="$FIX_RUNS_FAILED"
|
||||
out=$(run_with_mock --timeout 5 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "CI failure exits 1" 1 "$rc"
|
||||
assert_match "CI failure message includes conclusion" "failure" "$out"
|
||||
|
||||
# --- In-progress -> timeout (no polling loop, just immediate timeout) ---
|
||||
export MOCK_RUNS_BODY="$FIX_RUNS_INPROGRESS"
|
||||
start_t=$(date +%s)
|
||||
out=$(run_with_mock --timeout 2 --interval 1 test/repo 2>&1); rc=$?
|
||||
end_t=$(date +%s)
|
||||
elapsed=$(( end_t - start_t ))
|
||||
assert_exit "in_progress times out -> exit 1" 1 "$rc"
|
||||
assert_match "timeout error message" "timeout after 2s" "$out"
|
||||
assert_between "elapsed within tolerance" 2 4 "$elapsed"
|
||||
|
||||
# --- No matching workflow in runs -> timeout ---
|
||||
export MOCK_RUNS_BODY="$FIX_RUNS_WRONG_WORKFLOW"
|
||||
out=$(run_with_mock --timeout 2 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "no matching workflow times out -> exit 1" 1 "$rc"
|
||||
|
||||
# --- Empty runs list -> timeout ---
|
||||
export MOCK_RUNS_BODY="$FIX_RUNS_EMPTY"
|
||||
out=$(run_with_mock --timeout 1 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "empty runs times out -> exit 1" 1 "$rc"
|
||||
|
||||
# --- Branch API auth errors -> exit 3 ---
|
||||
export MOCK_GLOBAL_CODE=401
|
||||
out=$(run_with_mock --timeout 5 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "401 on branch lookup exits 3" 3 "$rc"
|
||||
assert_match "401 error message" "authentication failed" "$out"
|
||||
|
||||
export MOCK_GLOBAL_CODE=403
|
||||
out=$(run_with_mock --timeout 5 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "403 exits 3" 3 "$rc"
|
||||
|
||||
export MOCK_GLOBAL_CODE=404
|
||||
out=$(run_with_mock --timeout 5 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "404 exits 3" 3 "$rc"
|
||||
assert_match "404 error message" "not found" "$out"
|
||||
|
||||
# --- Runs API 401 -> exit 3 ---
|
||||
unset MOCK_GLOBAL_CODE
|
||||
export MOCK_BRANCH_BODY="$FIX_BRANCH"
|
||||
export MOCK_BRANCH_CODE=200
|
||||
export MOCK_RUNS_CODE=401
|
||||
out=$(run_with_mock --timeout 5 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "runs 401 exits 3" 3 "$rc"
|
||||
|
||||
# --- Transient 500 on runs -> retry until timeout ---
|
||||
export MOCK_RUNS_CODE=500
|
||||
out=$(run_with_mock --timeout 2 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "persistent 500 on runs exits 1 (timeout)" 1 "$rc"
|
||||
|
||||
# --- Network error -> retry until timeout ---
|
||||
unset MOCK_RUNS_CODE
|
||||
export MOCK_CURL_FAIL=1
|
||||
out=$(run_with_mock --timeout 2 --interval 1 test/repo 2>&1); rc=$?
|
||||
assert_exit "network error exits 3 (curl fail on branch)" 3 "$rc"
|
||||
unset MOCK_CURL_FAIL
|
||||
|
||||
# --- With explicit token ---
|
||||
unset MOCK_BRANCH_CODE MOCK_RUNS_CODE MOCK_GLOBAL_CODE
|
||||
export MOCK_RUNS_BODY="$FIX_RUNS_SUCCESS"
|
||||
export MOCK_RELEASE_BODY="$FIX_RELEASE"
|
||||
out=$(run_with_mock --token secret123 --timeout 5 --interval 1 test/repo 2>/dev/null)
|
||||
assert_eq "polling with --token still works" "v1.2.3.45" "$out"
|
||||
|
||||
# --- Summary ---
|
||||
echo
|
||||
total=$(( pass + fail ))
|
||||
echo -e "Results: ${GREEN}${pass}${NC}/${total} passed"
|
||||
if (( fail > 0 )); then
|
||||
echo -e "${RED}${fail} test(s) failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user