#!/usr/bin/env bash set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" WAIT="$SCRIPT_DIR/scripts/wait-for-release" 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" # Mock curl: emits fixture body then "\n", ignoring all flags except URL. 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_LOG:-}" ]] && echo "$url" >> "$MOCK_CURL_LOG" [[ -n "${MOCK_CURL_FAIL:-}" ]] && exit 7 if [[ -n "${MOCK_CURL_BODY:-}" && -f "$MOCK_CURL_BODY" ]]; then cat "$MOCK_CURL_BODY" fi printf '\n%s' "${MOCK_CURL_CODE:-200}" MOCK chmod +x "$MOCK_DIR/curl" run_with_mock() { PATH="$MOCK_DIR:$PATH" "$WAIT" "$@" } # Fixtures FIX_MATCH="$tmp_root/fix_match.json" cat > "$FIX_MATCH" <<'JSON' [ {"id":10,"tag_name":"v1.2.3.45","name":"v1.2.3.45"}, {"id":9,"tag_name":"v1.2.2.40","name":"v1.2.2.40"}, {"id":8,"tag_name":"v1.1.0.30","name":"v1.1.0.30"} ] JSON FIX_NO_MATCH="$tmp_root/fix_no_match.json" cat > "$FIX_NO_MATCH" <<'JSON' [ {"id":1,"tag_name":"v0.1.0.3","name":"v0.1.0.3"} ] JSON FIX_EMPTY="$tmp_root/fix_empty.json" echo "[]" > "$FIX_EMPTY" echo "=== wait-for-release 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 mentions PATTERN" "PATTERN" "$out" assert_match "help lists exit codes" "Exit codes" "$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" owner/repo 2>&1); rc=$? assert_exit "no pattern exits 2" 2 "$rc" assert_match "no pattern error" "PATTERN is required" "$out" out=$("$WAIT" noslash 'v*' 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 'v*' 2>&1); rc=$? assert_exit "non-numeric timeout exits 2" 2 "$rc" out=$("$WAIT" --interval 0 owner/repo 'v*' 2>&1); rc=$? assert_exit "interval 0 exits 2" 2 "$rc" out=$("$WAIT" --unknown-flag owner/repo 'v*' 2>&1); rc=$? assert_exit "unknown flag exits 2" 2 "$rc" # --- Dryrun --- out=$("$WAIT" --dryrun myorg/myrepo 'v1.2.3.*') 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 pattern" 'Pattern: v1\.2\.3\.\*' "$out" assert_match "dryrun shows timeout default" "Timeout: 600s" "$out" assert_match "dryrun shows interval default" "Interval: 5s" "$out" assert_match "dryrun shows API URL" "api/v1/repos/myorg/myrepo/releases" "$out" # Auth: no (no token anywhere) out=$(env -u GITEA_TOKEN -u GITHUB_TOKEN "$WAIT" --dryrun myorg/myrepo 'v*') assert_match "no token -> Auth: no" "Auth: no" "$out" # Auth: yes via --token out=$(env -u GITEA_TOKEN -u GITHUB_TOKEN "$WAIT" --dryrun -t tok123 myorg/myrepo 'v*') 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 'v*') assert_match "GITEA_TOKEN env -> Auth: yes" "Auth: yes" "$out" # --server override appears in URL out=$("$WAIT" --dryrun --server https://example.test myorg/myrepo 'v*') assert_match "custom --server in URL" "https://example\.test/api/v1/repos/myorg/myrepo/releases" "$out" # --- Real match via mock curl --- export MOCK_CURL_BODY="$FIX_MATCH" export MOCK_CURL_CODE=200 out=$(run_with_mock --timeout 5 --interval 1 test/repo 'v1.2.3.*') rc=$? assert_exit "immediate match exits 0" 0 "$rc" assert_eq "immediate match prints tag" "v1.2.3.45" "$out" out=$(run_with_mock --timeout 5 --interval 1 test/repo 'v1.*') assert_eq "wildcard v1.* matches newest" "v1.2.3.45" "$out" out=$(run_with_mock --timeout 5 --interval 1 test/repo 'v1.2.2.40') assert_eq "exact tag match" "v1.2.2.40" "$out" out=$(run_with_mock --timeout 5 --interval 1 test/repo 'v1.1.?.30') assert_eq "? wildcard matches" "v1.1.0.30" "$out" # --- No match -> timeout (short) --- export MOCK_CURL_BODY="$FIX_NO_MATCH" start_t=$(date +%s) out=$(run_with_mock --timeout 2 --interval 1 test/repo 'v9.*' 2>&1); rc=$? end_t=$(date +%s) elapsed=$(( end_t - start_t )) assert_exit "no match exits 1" 1 "$rc" assert_match "no match: timeout error" "timeout after 2s" "$out" assert_between "timeout elapsed within tolerance" 2 4 "$elapsed" # --- Empty releases list -> timeout --- export MOCK_CURL_BODY="$FIX_EMPTY" out=$(run_with_mock --timeout 1 --interval 1 test/repo 'v1.*' 2>&1); rc=$? assert_exit "empty list exits 1 (timeout)" 1 "$rc" # --- 401 -> exit 3 --- export MOCK_CURL_BODY="$FIX_MATCH" export MOCK_CURL_CODE=401 out=$(run_with_mock --timeout 5 --interval 1 test/repo 'v*' 2>&1); rc=$? assert_exit "401 exits 3" 3 "$rc" assert_match "401 error message" "authentication failed" "$out" # --- 403 -> exit 3 --- export MOCK_CURL_CODE=403 out=$(run_with_mock --timeout 5 --interval 1 test/repo 'v*' 2>&1); rc=$? assert_exit "403 exits 3" 3 "$rc" # --- 404 -> exit 3 --- export MOCK_CURL_CODE=404 out=$(run_with_mock --timeout 5 --interval 1 test/repo 'v*' 2>&1); rc=$? assert_exit "404 exits 3" 3 "$rc" assert_match "404 error message" "not found" "$out" # --- Transient 500 -> retry until timeout --- export MOCK_CURL_CODE=500 out=$(run_with_mock --timeout 2 --interval 1 test/repo 'v*' 2>&1); rc=$? assert_exit "persistent 500 exits 1 (timeout)" 1 "$rc" # --- Network error (curl fails) -> retry until timeout --- export MOCK_CURL_FAIL=1 unset MOCK_CURL_CODE out=$(run_with_mock --timeout 2 --interval 1 test/repo 'v*' 2>&1); rc=$? assert_exit "network error exits 1 (timeout)" 1 "$rc" unset MOCK_CURL_FAIL # --- Verbose emits polling info to stderr --- export MOCK_CURL_BODY="$FIX_NO_MATCH" export MOCK_CURL_CODE=200 stderr_out=$(run_with_mock --verbose --timeout 1 --interval 1 test/repo 'v99.*' 2>&1 >/dev/null || true) assert_match "verbose mentions poll" "poll " "$stderr_out" # --- Polling still succeeds when --token is provided --- export MOCK_CURL_BODY="$FIX_MATCH" export MOCK_CURL_CODE=200 out=$(run_with_mock --token secret123 --timeout 5 --interval 1 test/repo 'v1.2.3.*') assert_eq "polling with token still matches" "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