11#! /usr/bin/env bash
22# tests/scripts/test-ci-status-auth-ratelimit.sh
3- # Tests for auth pre-flight check , rate-limit detection, and max-iteration ceiling
4- # in scripts/ci-status.sh.
3+ # Behavioral tests for auth pre-flight, rate-limit detection, and max-iteration
4+ # ceiling in scripts/ci-status.sh.
55#
6- # Ticket: lockpick-doc-to-logic-vphc
7- #
8- # Tests:
9- # 1. test_auth_check_present — script contains gh auth status call
10- # 2. test_auth_failure_exits_fast — unauthenticated gh exits 1 with clear message
11- # 3. test_rate_limit_detection — script contains rate-limit/429/backoff detection
12- # 4. test_max_iterations_present — script contains MAX_POLL_ITERATIONS or max_iterations
13- # 5. test_max_iterations_ceiling — polling loop exits after max iterations (not infinite)
14- # 6. test_rate_limit_grep_patterns — acceptance criteria grep patterns pass
6+ # All tests invoke ci-status.sh with stubbed gh/git/sleep binaries to verify
7+ # actual behavior rather than grepping source code.
158#
169# Usage: bash tests/scripts/test-ci-status-auth-ratelimit.sh
17- # Returns: exit 0 if all tests pass, exit 1 if any fail
1810
1911set -uo pipefail
2012
@@ -29,109 +21,73 @@ CI_STATUS_SH="$DSO_PLUGIN_DIR/scripts/ci-status.sh"
2921
3022echo " === test-ci-status-auth-ratelimit.sh ==="
3123
32- # =============================================================================
33- # Test 1: auth check present in script
34- # ci-status.sh must contain a gh auth check before any API calls.
35- # =============================================================================
36- echo " "
37- echo " --- auth check present ---"
38- _snapshot_fail
24+ _TMP=$( mktemp -d)
25+ trap ' rm -rf "$_TMP"' EXIT
3926
40- AUTH_CHECK_COUNT=$( grep -c " gh auth" " $CI_STATUS_SH " || echo " 0" )
41- assert_ne " test_auth_check_present: gh auth appears in ci-status.sh" " 0" " $AUTH_CHECK_COUNT "
27+ # ── Shared: fake sleep (instant) ─────────────────────────────────────────
28+ FAKE_SLEEP_DIR=" $_TMP /fake-sleep"
29+ mkdir -p " $FAKE_SLEEP_DIR "
30+ cat > " $FAKE_SLEEP_DIR /sleep" << 'FAKESLEEP '
31+ #!/usr/bin/env bash
32+ exit 0
33+ FAKESLEEP
34+ chmod +x " $FAKE_SLEEP_DIR /sleep"
4235
43- assert_pass_if_clean " auth check present in ci-status.sh"
36+ # ── Shared: fake git (returns fake SHA and repo root) ────────────────────
37+ FAKE_GIT_DIR=" $_TMP /fake-git"
38+ mkdir -p " $FAKE_GIT_DIR "
39+ cat > " $FAKE_GIT_DIR /git" << 'FAKEGIT '
40+ #!/usr/bin/env bash
41+ if [[ "$1" == "ls-remote" ]]; then
42+ echo "abc123def456 refs/heads/main"
43+ exit 0
44+ fi
45+ if [[ "$1" == "rev-parse" ]]; then
46+ if [[ "${2:-}" == "--show-toplevel" ]]; then
47+ echo "/tmp/fake-repo"
48+ exit 0
49+ fi
50+ echo "abc123def456"
51+ exit 0
52+ fi
53+ exit 0
54+ FAKEGIT
55+ chmod +x " $FAKE_GIT_DIR /git"
4456
4557# =============================================================================
46- # Test 2: auth failure causes fast exit with clear message
47- # When gh auth status returns non-zero, the script should exit 1 with a
48- # clear error message (not loop forever).
49- # We test this by stubbing gh to fail auth status and verifying exit code.
58+ # Test 1: Auth failure exits non-zero with clear error message
5059# =============================================================================
51- echo " "
52- echo " --- auth failure exits fast ---"
53- _snapshot_fail
54-
55- _OUTER_TMP=$( mktemp -d)
56- trap ' rm -rf "$_OUTER_TMP"' EXIT
57-
58- # Create a fake gh that fails on "auth status" but otherwise does nothing
59- FAKE_GH_DIR=" $_OUTER_TMP /fake-bin"
60- mkdir -p " $FAKE_GH_DIR "
61- cat > " $FAKE_GH_DIR /gh" << 'FAKEGH '
60+ FAKE_GH_AUTH=" $_TMP /fake-gh-auth"
61+ mkdir -p " $FAKE_GH_AUTH "
62+ cat > " $FAKE_GH_AUTH /gh" << 'FAKEGH '
6263#!/usr/bin/env bash
63- # Fake gh: auth status fails, everything else succeeds vacuously
6464if [[ "$1 $2" == "auth status" ]]; then
6565 echo "You are not logged into any GitHub hosts. Run gh auth login to authenticate." >&2
6666 exit 1
6767fi
68- # Any other gh call returns empty JSON to avoid hanging
69- if [[ "$1" == "run" ]]; then
70- echo "[]"
71- fi
68+ if [[ "$1" == "run" ]]; then echo "[]"; fi
7269exit 0
7370FAKEGH
74- chmod +x " $FAKE_GH_DIR /gh"
75-
76- # Run ci-status.sh with the fake gh in PATH
77- result=$( PATH=" $FAKE_GH_DIR :$PATH " bash " $CI_STATUS_SH " 2>&1 ) || auth_exit=$?
78- auth_exit=" ${auth_exit:- 0} "
71+ chmod +x " $FAKE_GH_AUTH /gh"
7972
80- assert_ne " test_auth_failure_exits_nonzero: exits non-zero when unauthenticated " " 0 " " $ auth_exit"
81- assert_contains " test_auth_failure_message: output mentions authentication " " auth " " $result "
73+ auth_exit=0
74+ result= $( PATH= " $FAKE_GH_AUTH : $PATH " bash " $CI_STATUS_SH " 2>&1 ) || auth_exit= $?
8275
83- assert_pass_if_clean " auth failure exits fast with clear message"
76+ assert_ne " test_auth_failure_exits_nonzero" " 0" " $auth_exit "
77+ assert_contains " test_auth_failure_mentions_auth" " auth" " $result "
8478
8579# =============================================================================
86- # Test 3: rate-limit detection present in script
87- # ci-status.sh must contain references to rate limiting (429, rate limit, or backoff).
80+ # Test 2: Max iterations ceiling causes exit (not infinite loop)
81+ # Stub gh to always return in_progress. The script should exit after
82+ # MAX_POLL_ITERATIONS, not loop forever.
8883# =============================================================================
89- echo " "
90- echo " --- rate-limit detection present ---"
91- _snapshot_fail
92-
93- RATELIMIT_COUNT=$( grep -cE " rate.limit|429|backoff" " $CI_STATUS_SH " || echo " 0" )
94- assert_ne " test_rate_limit_detection: rate-limit/429/backoff appears in ci-status.sh" " 0" " $RATELIMIT_COUNT "
95-
96- assert_pass_if_clean " rate-limit detection present in ci-status.sh"
97-
98- # =============================================================================
99- # Test 4: MAX_POLL_ITERATIONS or max_iterations present
100- # =============================================================================
101- echo " "
102- echo " --- max iterations ceiling present ---"
103- _snapshot_fail
104-
105- MAX_ITER_COUNT=$( grep -cE " MAX_POLL_ITERATIONS|max_iterations" " $CI_STATUS_SH " || echo " 0" )
106- assert_ne " test_max_iterations_present: MAX_POLL_ITERATIONS appears in ci-status.sh" " 0" " $MAX_ITER_COUNT "
107-
108- assert_pass_if_clean " MAX_POLL_ITERATIONS ceiling defined in ci-status.sh"
109-
110- # =============================================================================
111- # Test 5: Polling loop respects max iterations ceiling
112- # When the CI run stays in_progress for more than MAX_POLL_ITERATIONS iterations,
113- # the script should exit 1 with a timeout error instead of looping forever.
114- # We stub gh to always return in_progress and verify the script exits within
115- # a reasonable number of gh calls.
116- # =============================================================================
117- echo " "
118- echo " --- max iterations causes exit (not infinite loop) ---"
119- _snapshot_fail
120-
121- FAKE_GH_DIR2=" $_OUTER_TMP /fake-bin2"
122- mkdir -p " $FAKE_GH_DIR2 "
123- CALL_COUNT_FILE=" $_OUTER_TMP /gh_call_count.txt"
84+ CALL_COUNT_FILE=" $_TMP /gh_call_count.txt"
12485echo " 0" > " $CALL_COUNT_FILE "
12586
126- # Create a fake gh that:
127- # - passes auth status
128- # - returns in_progress for all run list/view calls
129- # This forces the script into its polling loop, which should eventually exit
130- # due to the MAX_POLL_ITERATIONS ceiling (not run forever).
131- cat > " $FAKE_GH_DIR2 /gh" << FAKEGH2
87+ FAKE_GH_LOOP=" $_TMP /fake-gh-loop"
88+ mkdir -p " $FAKE_GH_LOOP "
89+ cat > " $FAKE_GH_LOOP /gh" << FAKEGH2
13290#!/usr/bin/env bash
133- # Fake gh for max-iterations test
134- # Count calls to detect infinite loops
13591_count=\$ (cat "$CALL_COUNT_FILE " 2>/dev/null || echo "0")
13692_count=\$ (( _count + 1 ))
13793echo "\$ _count" > "$CALL_COUNT_FILE "
@@ -140,112 +96,62 @@ if [[ "\$1 \$2" == "auth status" ]]; then
14096 echo "Logged in to github.com as testuser" >&2
14197 exit 0
14298fi
143-
144- # For ls-remote (called for SHA resolution), return nothing
145- if [[ "\$ 1" == "ls-remote" ]]; then
146- exit 0
147- fi
148-
149- # run list: return in_progress run (headSha must match fake git ls-remote output)
99+ if [[ "\$ 1" == "ls-remote" ]]; then exit 0; fi
150100if [[ "\$ 1" == "run" && "\$ 2" == "list" ]]; then
151101 echo '[{"databaseId":12345,"status":"in_progress","conclusion":null,"name":"CI","startedAt":"2020-01-01T00:00:00Z","createdAt":"2020-01-01T00:00:00Z","headSha":"abc123def456"}]'
152102 exit 0
153103fi
154-
155- # run view: always return in_progress
156104if [[ "\$ 1" == "run" && "\$ 2" == "view" ]]; then
157105 echo '{"status":"in_progress","conclusion":null,"name":"CI"}'
158106 exit 0
159107fi
160-
161108exit 0
162109FAKEGH2
163- chmod +x " $FAKE_GH_DIR2 /gh"
110+ chmod +x " $FAKE_GH_LOOP /gh"
164111
165- # Also stub git ls-remote and git rev-parse to avoid real git calls
166- FAKE_GIT_DIR=" $_OUTER_TMP /fake-git"
167- mkdir -p " $FAKE_GIT_DIR "
168- cat > " $FAKE_GIT_DIR /git" << 'FAKEGIT '
169- #!/usr/bin/env bash
170- # Fake git for iteration test
171- if [[ "$1" == "ls-remote" ]]; then
172- echo "abc123def456 refs/heads/main"
173- exit 0
174- fi
175- if [[ "$1" == "rev-parse" ]]; then
176- if [[ "$2" == "--show-toplevel" ]]; then
177- echo "/tmp/fake-repo"
178- exit 0
179- fi
180- echo "abc123def456"
181- exit 0
182- fi
183- exit 0
184- FAKEGIT
185- chmod +x " $FAKE_GIT_DIR /git"
186-
187- # Override sleep to be instant so the test runs fast
188- FAKE_SLEEP_DIR=" $_OUTER_TMP /fake-sleep"
189- mkdir -p " $FAKE_SLEEP_DIR "
190- cat > " $FAKE_SLEEP_DIR /sleep" << 'FAKESLEEP '
191- #!/usr/bin/env bash
192- # Fake sleep: instant
193- exit 0
194- FAKESLEEP
195- chmod +x " $FAKE_SLEEP_DIR /sleep"
196-
197- # Run with --wait mode and a controlled PATH
198- # The script should exit non-zero (timeout) rather than loop forever
199- # We use a generous real timeout as a safety net
200112iter_result=0
201113timeout 30 bash -c "
202- PATH='$FAKE_SLEEP_DIR :$FAKE_GH_DIR2 :$FAKE_GIT_DIR : $PATH '
114+ PATH='$FAKE_SLEEP_DIR :$FAKE_GH_LOOP :$FAKE_GIT_DIR ': \ $ PATH
203115 export PATH
204116 bash '$CI_STATUS_SH ' --wait --skip-regression-check --branch main 2>&1
205- " > " $_OUTER_TMP /iter_output.txt" 2>&1 || iter_result=$?
117+ " > " $_TMP /iter_output.txt" 2>&1 || iter_result=$?
206118
207- _iter_output=$( cat " $_OUTER_TMP /iter_output.txt" 2> /dev/null || echo " " )
208- _gh_calls=$( cat " $CALL_COUNT_FILE " 2> /dev/null || echo " 0" )
119+ _iter_output=$( cat " $_TMP /iter_output.txt" 2> /dev/null || echo " " )
209120
210- # The script must have exited (iter_result != 0 means exit 1 or timeout)
211- # Check it did NOT time out via our 30s watchdog (timeout exits 124)
212- assert_ne " test_max_iter_exited: script exited non-zero (not infinite loop)" " 0" " $iter_result "
121+ assert_ne " test_max_iter_exits_nonzero" " 0" " $iter_result "
213122
214- # If it timed out via our watchdog (exit 124), that means the ceiling didn't work
123+ # Should not have hit our 30s watchdog (exit 124 = timeout killed it)
215124_watchdog_timeout=0
216- if [[ " $iter_result " == " 124" ]]; then
217- _watchdog_timeout=1
218- fi
219- assert_eq " test_max_iter_not_watchdog: script exited before 30s watchdog" " 0" " $_watchdog_timeout "
125+ [[ " $iter_result " == " 124" ]] && _watchdog_timeout=1
126+ assert_eq " test_max_iter_not_watchdog" " 0" " $_watchdog_timeout "
220127
221- # Output should mention timeout or max iterations
222- assert_contains " test_max_iter_message: output contains TIMEOUT or iterations" " TIMEOUT" " $_iter_output "
223-
224- assert_pass_if_clean " max iterations causes exit, not infinite loop"
128+ assert_contains " test_max_iter_mentions_timeout" " TIMEOUT" " $_iter_output "
225129
226130# =============================================================================
227- # Test 6: Acceptance criteria grep patterns
228- # These directly verify the grep patterns from the ticket's acceptance criteria.
131+ # Test 3: Successful CI returns exit 0 with success status
229132# =============================================================================
230- echo " "
231- echo " --- acceptance criteria grep patterns ---"
232- _snapshot_fail
233-
234- # grep -q "gh auth" scripts/ci-status.sh
235- GH_AUTH_MATCH=$( grep -c " gh auth" " $CI_STATUS_SH " || echo " 0" )
236- assert_ne " acceptance_criteria_gh_auth" " 0" " $GH_AUTH_MATCH "
237-
238- # grep -q "rate.limit\|429\|backoff" scripts/ci-status.sh
239- RATELIMIT_MATCH=$( grep -cE " rate.limit|429|backoff" " $CI_STATUS_SH " || echo " 0" )
240- assert_ne " acceptance_criteria_ratelimit_429_backoff" " 0" " $RATELIMIT_MATCH "
133+ FAKE_GH_OK=" $_TMP /fake-gh-ok"
134+ mkdir -p " $FAKE_GH_OK "
135+ cat > " $FAKE_GH_OK /gh" << 'FAKEOK '
136+ #!/usr/bin/env bash
137+ if [[ "$1 $2" == "auth status" ]]; then echo "Logged in" >&2; exit 0; fi
138+ if [[ "$1" == "run" && "$2" == "list" ]]; then
139+ echo '[{"databaseId":1,"status":"completed","conclusion":"success","name":"CI","startedAt":"2020-01-01T00:00:00Z","createdAt":"2020-01-01T00:00:00Z","headSha":"abc123def456"}]'
140+ exit 0
141+ fi
142+ if [[ "$1" == "run" && "$2" == "view" ]]; then
143+ echo '{"status":"completed","conclusion":"success","name":"CI"}'
144+ exit 0
145+ fi
146+ exit 0
147+ FAKEOK
148+ chmod +x " $FAKE_GH_OK /gh"
241149
242- # grep -q "MAX_POLL_ITERATIONS\|max_iterations" scripts/ci-status.sh
243- MAXITER_MATCH=$( grep -cE " MAX_POLL_ITERATIONS|max_iterations" " $CI_STATUS_SH " || echo " 0" )
244- assert_ne " acceptance_criteria_max_poll_iterations" " 0" " $MAXITER_MATCH "
150+ ok_result=0
151+ ok_output=$( PATH=" $FAKE_GH_OK :$FAKE_GIT_DIR :$PATH " bash " $CI_STATUS_SH " --wait --skip-regression-check --branch main 2>&1 ) || ok_result=$?
245152
246- assert_pass_if_clean " all acceptance criteria grep patterns pass"
153+ assert_eq " test_success_exits_zero" " 0" " $ok_result "
154+ assert_contains " test_success_reports_success" " success" " $ok_output "
247155
248- # =============================================================================
249- # Summary
250156# =============================================================================
251157print_summary
0 commit comments