Skip to content

Commit 8b2549d

Browse files
locus313Copilot
andcommitted
feat: expand test suite to 91 tests covering all scripts and lib functions
test_common.bats (29 tests): - Add err, configure_gh_auth, validate_token, validate_github_token URL warning, get_repo_page_count (with/without Link header), gh_api_paginate (404, 422, single-page) - Upgrade _mock_curl to use universal mock_curl.sh (handles both gh_api stdout mode and gh_api_paginate/validate_token -o/-D file mode) tests/mock_curl.sh: - Universal drop-in curl mock; response data via env vars (MOCK_CURL_CODE, MOCK_CURL_BODY, MOCK_CURL_LINK) — no quoting issues with JSON bodies test_script_validation.bats (62 tests): - Every script: missing required env vars exit 1 in the correct order - Arg parsing: --help exits 0, unknown args exit 1, recognised flags (--dry-run, --type) don't trigger Unknown argument errors - Invalid enum values: --type, DEPENDABOT_REASON, SECRET_SCANNING_RESOLUTION - github-import-repo: non-GitHub GIT_URL_PREFIX exits 1 (security guard) - github-repo-permissions-report: missing -r exits 1 ci.yml: run bats tests/ (all bats files) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b4519f3 commit 8b2549d

4 files changed

Lines changed: 716 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ jobs:
4747
run: sudo apt-get update -qq && sudo apt-get install -y bats
4848

4949
- name: Run unit tests
50-
run: bats tests/test_common.bats
50+
run: bats tests/

tests/mock_curl.sh

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/bin/sh
2+
# =============================================================================
3+
# mock_curl.sh
4+
#
5+
# Universal drop-in curl mock for bats tests. Copy into a directory that is
6+
# prepended to PATH; the real curl is then shadowed for the duration of a test.
7+
#
8+
# Response data is read from environment variables so callers never embed
9+
# special characters in the script body:
10+
#
11+
# MOCK_CURL_CODE HTTP status code to return (default: 200)
12+
# MOCK_CURL_BODY Response body (default: empty)
13+
# MOCK_CURL_LINK Full URL for Link: next header — set to make the
14+
# response look like a paginated "non-last" page;
15+
# leave empty (default) to signal the final page
16+
#
17+
# Handles two calling conventions used in lib/github-common.sh:
18+
#
19+
# stdout mode (gh_api, get_repo_page_count)
20+
# curl ... (no -o flag)
21+
# Output: <body>\n<code> — gh_api splits on the last line
22+
#
23+
# file mode (gh_api_paginate, validate_token)
24+
# curl ... -o <body-file> [-D <headers-file>]
25+
# Output: <code> (body written to -o file; headers written to -D file)
26+
# =============================================================================
27+
28+
HFILE=""
29+
BFILE=""
30+
31+
# Parse only the flags we care about; everything else is ignored
32+
while [ $# -gt 0 ]; do
33+
case "$1" in
34+
-D) HFILE="$2"; shift 2 ;;
35+
-o) BFILE="$2"; shift 2 ;;
36+
*) shift ;;
37+
esac
38+
done
39+
40+
CODE="${MOCK_CURL_CODE:-200}"
41+
42+
if [ -n "$BFILE" ]; then
43+
# File mode: write body to -o target, write headers to -D target (if set)
44+
printf '%s' "${MOCK_CURL_BODY:-}" > "$BFILE"
45+
if [ -n "$HFILE" ]; then
46+
printf 'HTTP/1.1 %s\r\n' "$CODE" > "$HFILE"
47+
[ -n "${MOCK_CURL_LINK:-}" ] && \
48+
printf 'link: <%s>; rel="next"\r\n' "${MOCK_CURL_LINK:-}" >> "$HFILE"
49+
printf '\r\n' >> "$HFILE"
50+
fi
51+
printf '%s' "$CODE"
52+
else
53+
# Stdout mode: body on first line(s), status code on last line
54+
printf '%s\n%s' "${MOCK_CURL_BODY:-}" "$CODE"
55+
fi

tests/test_common.bats

Lines changed: 185 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
# =============================================================================
33
# tests/test_common.bats
44
#
5-
# Unit tests for lib/github-common.sh — pure-logic functions and gh_api
6-
# sentinel returns. No real network calls are made; curl is mocked per test.
5+
# Unit tests for lib/github-common.sh — pure-logic functions and API helpers.
6+
# No real network calls are made; curl and gh are mocked per test via a
7+
# temporary directory prepended to PATH.
78
#
89
# Requirements:
910
# - bats (https://github.com/bats-core/bats-core / apt install bats)
@@ -14,29 +15,45 @@
1415

1516
LIB_PATH="${BATS_TEST_DIRNAME}/../lib/github-common.sh"
1617

17-
# Per-test mock binary directory; prepend to PATH in subshells that need mocking
1818
MOCK_BIN=""
1919

2020
setup() {
2121
MOCK_BIN="$(mktemp -d)"
22+
# Default gh mock: fails all calls so GITHUB_TOKEN is not auto-resolved
23+
printf '#!/bin/sh\nexit 1\n' > "$MOCK_BIN/gh"
24+
chmod +x "$MOCK_BIN/gh"
2225
}
2326

2427
teardown() {
2528
rm -rf "$MOCK_BIN"
2629
}
2730

28-
# Write a mock curl that outputs <body>\n<status_code> and ignores all arguments.
29-
# gh_api captures: body=$(curl -s -w "\n%{http_code}" ...) then splits on last line.
30-
# Body and status are passed via env vars to avoid quoting issues with JSON content.
31+
# Install the universal curl mock. Passes response data via env vars so no
32+
# quoting issues arise with JSON or special characters in the body.
33+
# $1 HTTP status code (default: 200)
34+
# $2 Response body (default: empty)
35+
# $3 Link: next URL (default: empty = last page)
3136
_mock_curl() {
32-
local code="$1" body="${2:-}"
37+
local code="${1:-200}" body="${2:-}" link="${3:-}"
3338
export MOCK_CURL_CODE="$code"
3439
export MOCK_CURL_BODY="$body"
35-
printf '#!/bin/sh\nprintf "%%s\\n%%s" "$MOCK_CURL_BODY" "$MOCK_CURL_CODE"\n' \
36-
> "$MOCK_BIN/curl"
40+
export MOCK_CURL_LINK="$link"
41+
cp "${BATS_TEST_DIRNAME}/mock_curl.sh" "$MOCK_BIN/curl"
3742
chmod +x "$MOCK_BIN/curl"
3843
}
3944

45+
# ─── err ──────────────────────────────────────────────────────────────────────
46+
47+
@test "err: exits 1" {
48+
run bash -c "GITHUB_TOKEN=x source '${LIB_PATH}' 2>/dev/null; err 'something went wrong'"
49+
[ "$status" -eq 1 ]
50+
}
51+
52+
@test "err: output contains the supplied message" {
53+
run bash -c "GITHUB_TOKEN=x source '${LIB_PATH}' 2>/dev/null; err 'something went wrong'" 2>&1
54+
[[ "$output" == *"something went wrong"* ]]
55+
}
56+
4057
# ─── validate_slug ────────────────────────────────────────────────────────────
4158

4259
@test "validate_slug: alphanumeric slug passes" {
@@ -103,6 +120,110 @@ _mock_curl() {
103120
[ "$status" -eq 1 ]
104121
}
105122

123+
# ─── configure_gh_auth ────────────────────────────────────────────────────────
124+
125+
@test "configure_gh_auth: passes and exports GH_TOKEN when GITHUB_TOKEN is set" {
126+
run bash -c "
127+
export PATH='${MOCK_BIN}:${PATH}'
128+
export GITHUB_TOKEN=mytoken
129+
source '${LIB_PATH}' 2>/dev/null
130+
configure_gh_auth
131+
[ \"\$GH_TOKEN\" = 'mytoken' ]
132+
"
133+
[ "$status" -eq 0 ]
134+
}
135+
136+
@test "configure_gh_auth: exits 1 when GITHUB_TOKEN unset and gh auth fails" {
137+
run bash -c "
138+
export PATH='${MOCK_BIN}:${PATH}'
139+
unset GITHUB_TOKEN
140+
source '${LIB_PATH}' 2>/dev/null
141+
configure_gh_auth
142+
"
143+
[ "$status" -eq 1 ]
144+
}
145+
146+
# ─── validate_token / validate_github_token ───────────────────────────────────
147+
148+
@test "validate_token: exits 0 on HTTP 200" {
149+
_mock_curl 200
150+
run bash -c "
151+
export PATH='${MOCK_BIN}:${PATH}'
152+
export GITHUB_TOKEN=fake
153+
export API_URL_PREFIX=https://api.github.com
154+
source '${LIB_PATH}' 2>/dev/null
155+
validate_token GITHUB_TOKEN
156+
"
157+
[ "$status" -eq 0 ]
158+
}
159+
160+
@test "validate_token: exits 1 on HTTP 401" {
161+
_mock_curl 401
162+
run bash -c "
163+
export PATH='${MOCK_BIN}:${PATH}'
164+
export GITHUB_TOKEN=fake
165+
export API_URL_PREFIX=https://api.github.com
166+
source '${LIB_PATH}' 2>/dev/null
167+
validate_token GITHUB_TOKEN
168+
"
169+
[ "$status" -eq 1 ]
170+
}
171+
172+
@test "validate_github_token: emits warning for non-GitHub API_URL_PREFIX" {
173+
_mock_curl 200
174+
run bash -c "
175+
export PATH='${MOCK_BIN}:${PATH}'
176+
export GITHUB_TOKEN=fake
177+
export API_URL_PREFIX=https://not-github.example.com
178+
source '${LIB_PATH}' 2>/dev/null
179+
validate_github_token
180+
"
181+
[ "$status" -eq 0 ]
182+
[[ "$output" == *"does not look like"* ]]
183+
}
184+
185+
@test "validate_github_token: no warning for api.github.com" {
186+
_mock_curl 200
187+
run bash -c "
188+
export PATH='${MOCK_BIN}:${PATH}'
189+
export GITHUB_TOKEN=fake
190+
export API_URL_PREFIX=https://api.github.com
191+
source '${LIB_PATH}' 2>/dev/null
192+
validate_github_token
193+
"
194+
[ "$status" -eq 0 ]
195+
[[ "$output" != *"does not look like"* ]]
196+
}
197+
198+
# ─── get_repo_page_count ──────────────────────────────────────────────────────
199+
200+
@test "get_repo_page_count: returns 1 when there is no Link header" {
201+
_mock_curl 200
202+
run bash -c "
203+
export PATH='${MOCK_BIN}:${PATH}'
204+
export GITHUB_TOKEN=fake
205+
export API_URL_PREFIX=https://api.github.com
206+
source '${LIB_PATH}' 2>/dev/null
207+
get_repo_page_count 'https://api.github.com/orgs/test/repos?per_page=100'
208+
"
209+
[ "$status" -eq 0 ]
210+
[ "$output" = "1" ]
211+
}
212+
213+
@test "get_repo_page_count: returns last page number from Link header" {
214+
# Body contains a Link header line — get_repo_page_count greps stdout for &page=N
215+
_mock_curl 200 'Link: <https://api.github.com/orgs/test/repos?per_page=100&page=7>; rel="last"'
216+
run bash -c "
217+
export PATH='${MOCK_BIN}:${PATH}'
218+
export GITHUB_TOKEN=fake
219+
export API_URL_PREFIX=https://api.github.com
220+
source '${LIB_PATH}' 2>/dev/null
221+
get_repo_page_count 'https://api.github.com/orgs/test/repos?per_page=100'
222+
"
223+
[ "$status" -eq 0 ]
224+
[ "$output" = "7" ]
225+
}
226+
106227
# ─── gh_api sentinels ─────────────────────────────────────────────────────────
107228

108229
@test "gh_api: returns __404__ on HTTP 404" {
@@ -126,3 +247,58 @@ _mock_curl() {
126247
[ "$status" -eq 0 ]
127248
[ "$output" = '{"login":"test-org"}' ]
128249
}
250+
251+
@test "gh_api: prepends API_URL_PREFIX when path starts with /" {
252+
_mock_curl 200 '{"ok":true}'
253+
run bash -c "
254+
export PATH='${MOCK_BIN}:${PATH}'
255+
export GITHUB_TOKEN=fake
256+
export API_URL_PREFIX=https://api.github.com
257+
source '${LIB_PATH}' 2>/dev/null
258+
result=\$(gh_api '/some/path')
259+
[ \"\$result\" = '{\"ok\":true}' ]
260+
"
261+
[ "$status" -eq 0 ]
262+
}
263+
264+
# ─── gh_api_paginate ──────────────────────────────────────────────────────────
265+
266+
@test "gh_api_paginate: exits 0 silently on HTTP 404" {
267+
_mock_curl 404
268+
run bash -c "
269+
export PATH='${MOCK_BIN}:${PATH}'
270+
export GITHUB_TOKEN=fake
271+
export API_URL_PREFIX=https://api.github.com
272+
source '${LIB_PATH}' 2>/dev/null
273+
gh_api_paginate '/orgs/nonexistent/repos'
274+
"
275+
[ "$status" -eq 0 ]
276+
[ -z "$output" ]
277+
}
278+
279+
@test "gh_api_paginate: exits 0 silently on HTTP 422" {
280+
_mock_curl 422
281+
run bash -c "
282+
export PATH='${MOCK_BIN}:${PATH}'
283+
export GITHUB_TOKEN=fake
284+
export API_URL_PREFIX=https://api.github.com
285+
source '${LIB_PATH}' 2>/dev/null
286+
gh_api_paginate '/orgs/test/repos'
287+
"
288+
[ "$status" -eq 0 ]
289+
[ -z "$output" ]
290+
}
291+
292+
@test "gh_api_paginate: outputs items from a single page" {
293+
_mock_curl 200 '[{"name":"repo-a"},{"name":"repo-b"}]'
294+
run bash -c "
295+
export PATH='${MOCK_BIN}:${PATH}'
296+
export GITHUB_TOKEN=fake
297+
export API_URL_PREFIX=https://api.github.com
298+
source '${LIB_PATH}' 2>/dev/null
299+
gh_api_paginate '/orgs/test/repos'
300+
"
301+
[ "$status" -eq 0 ]
302+
[[ "$output" == *'"name":"repo-a"'* ]]
303+
[[ "$output" == *'"name":"repo-b"'* ]]
304+
}

0 commit comments

Comments
 (0)