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
311 lines
10 KiB
Bash
Executable File
311 lines
10 KiB
Bash
Executable File
#!/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
|