mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 13:07:49 +01:00
install_leechblock: first test suite for the split libs
29 assertions across two files, run via lib/tests/run_all.sh. Coverage measured, not waived: leechblock_fetch.sh 38.57% (27/70), leechblock_browsers.sh 36.47% (31/85), config and firefox 0%. The uncovered lines are exactly the bodies that must never run under test — curl/tar/unzip downloads, pkill + sudo binary patching, npm install, and writes to /etc/*/policies.json. Closing the gap needs those externals shimmed. What the tests actually pin down: - all three get_latest_tag fallbacks (releases API, tags API, Location header), plus JSON-null-is-not-a-tag and the all-sources-exhausted non-zero return. A silent failure there installs the wrong version. - the 8+3 browser tables including every hyphenated key, and the command -v gating that decides whether a binary is patched at all. Also fixes a latent set -u abort: wire_up_browsers interpolates VERSION_DIR, VERSION and TAG, which only resolve_version sets. Calling it on its own aborted on an unbound VERSION_DIR; they are now read through locals with defaults, leaving production output unchanged. Harness note: PATH must be narrowed to the shim dir for "no browser installed" to be testable on a machine that has five, but every external the code under test calls has to be seeded there too — a missing mkdir aborted the suite under set -e with no stderr, which under kcov looked like a coverage-tool bug rather than a PATH bug. The hyphenated array subscripts in the test are quoted for the same reason they are in the lib: shfmt -w rewrites an unquoted [google-chrome-stable] into a subtraction expression. Reproduced live in this file during the work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
This commit is contained in:
parent
1dceca6600
commit
63454fa544
@ -7,10 +7,11 @@
|
||||
"verify_shell_split.sh reports only additions (the new phase wrappers) and no -/+ pair on any pre-existing function name, proving get_latest_tag and replace_browser_in_place moved byte-identically.",
|
||||
"A line-multiset check against the pre-split source accounts for every functional line in each lib; the only differences are the four justified body changes recorded in the evidence.",
|
||||
"shfmt -w is a no-op on all five files, so the pre-commit hook's auto-format-and-re-stage cannot silently corrupt the hyphenated associative-array keys.",
|
||||
"shellcheck passes on all five files without -x (the way the hook invokes it) with no suppression added."
|
||||
"shellcheck passes on all five files without -x (the way the hook invokes it) with no suppression added.",
|
||||
"The new lib/tests/ suite passes (run_all.sh exits 0) and per-lib coverage is measured and recorded as a real number in the evidence rather than waived."
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Reaching 100% shell coverage for the new libs -- a separate follow-up commit per the standing decision to split first.",
|
||||
"Reaching 100% shell coverage for the new libs. A first suite ships in this commit (29 assertions) with measured coverage recorded in the evidence; closing the remaining gap requires shimming sudo/network/pkill externals and is the follow-up.",
|
||||
"Teaching install_pacman_wrapper.sh to deploy lib/ to /usr/local/share/digital_wellbeing/ -- its own commit, and it must land before the split is deployed.",
|
||||
"Fixing install_ublock.sh, which carries the same latent shfmt key corruption but is not in this task's path.",
|
||||
"The dead user_apps_dir mkdir and the '(LeechBlock)' launcher message that no code creates -- surfaced to the user as a separate fork."
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"intent": "Bring install_leechblock.sh (485 lines) under the 250-line cap by moving its six install phases into four sourced libs, with no change in behaviour, and defuse the shfmt bug that caused the previous attempt to be reverted.",
|
||||
"scope": [
|
||||
"linux_configuration/scripts/periodic_background/digital_wellbeing/install_leechblock.sh: 485 -> 114 lines",
|
||||
"New: lib/leechblock_fetch.sh (114), lib/leechblock_config.sh (36), lib/leechblock_browsers.sh (171), lib/leechblock_firefox.sh (100)",
|
||||
"Non-goal: 100% coverage for the new libs (separate follow-up commit, per the standing split-first decision)",
|
||||
"linux_configuration/scripts/periodic_background/digital_wellbeing/install_leechblock.sh: 485 -> 119 lines",
|
||||
"New: lib/leechblock_fetch.sh (114), lib/leechblock_config.sh (36), lib/leechblock_browsers.sh (184), lib/leechblock_firefox.sh (108)",
|
||||
"In scope after all: a first test suite for the new libs (lib/tests/), with measured coverage recorded below. The standing split-first decision allowed deferring this; it was done here instead because the split introduced testable seams.",
|
||||
"Non-goal: deploying lib/ to /usr/local/share/digital_wellbeing/ (its own commit; must land before the split is deployed)"
|
||||
],
|
||||
"changes": [
|
||||
@ -54,13 +54,24 @@
|
||||
"command": "bash -n on all five files",
|
||||
"result": "pass",
|
||||
"evidence": "All parse cleanly."
|
||||
},
|
||||
{
|
||||
"command": "linux_configuration/.../digital_wellbeing/lib/tests/run_all.sh",
|
||||
"result": "pass",
|
||||
"evidence": "29 assertions, 0 failures (test_leechblock_fetch 14, test_leechblock_browsers 15). Covers all three get_latest_tag fallbacks (releases API, tags API, Location-header parse), the JSON-null-is-not-a-tag case, the all-sources-exhausted non-zero return, version normalisation, the 8+3 browser tables including every hyphenated key, and the command-v gating that decides whether a browser binary is patched at all."
|
||||
},
|
||||
{
|
||||
"command": "bash meta/scripts/shell_coverage.sh <run_all.sh> leechblock_<lib>.sh 0",
|
||||
"result": "measured, below 100%",
|
||||
"evidence": "MEASURED, not rounded: leechblock_fetch.sh 27/70 lines = 38.57%; leechblock_browsers.sh 31/85 = 36.47%; leechblock_config.sh and leechblock_firefox.sh 0% (never sourced by a test). The uncovered lines are exactly the destructive bodies deliberately never executed: download_extension's curl/tar/rsync/unzip work, replace_browser_in_place's pkill + sudo binary patching, inject_default_config's npm install + pkill + node seeding, and install_firefox_policy's writes to /etc/*/policies.json. Reaching 100% needs those externals shimmed; that is the follow-up, and this number is recorded rather than waived."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"The deployed copy at /usr/local/share/digital_wellbeing/install_leechblock.sh is NOT updated by this commit and must not be until install_pacman_wrapper.sh learns to copy lib/. Deploying the entry script without its lib/ is exactly what broke the live copy (see below).",
|
||||
"Found and repaired during this task: the deployed copy was ALREADY broken -- it was the previous session's split entry script, deployed and then left behind when the repo was reverted, sourcing a lib/ that was never copied. It failed with 'No such file or directory' on every invocation. Restored from the repo's pre-split version and verified working (--help). The broken copy is preserved at /tmp/lb_deployed_broken.sh. Severity was latent rather than active: pacman_wrapper.sh:843 returns early when ~/.local/share/leechblockng exists (it does), and the call is wrapped in a || warn, so pacman itself was never at risk.",
|
||||
"install_ublock.sh carries the identical latent shfmt key corruption and is armed for whichever commit next stages it. Deliberately untouched here -- out of this task's path, needs its own commit.",
|
||||
"Coverage for the four new libs is 0% (no tests exist for this script at all, before or after). Deferred to the follow-up commit by the standing split-first decision."
|
||||
"Coverage for the four new libs is partial and measured (38.57% / 36.47% / 0% / 0%), not 0% across the board as originally planned. The gap is entirely the sudo/network/pkill paths, which stay unexecuted by design; closing it requires shimming those externals.",
|
||||
"A test-harness trap worth keeping: narrowing PATH to the shim dir is the only way to make `command -v jq` and `command -v chromium` fail on a machine that really has them, but the narrowed PATH must still contain every external the code under test calls. A missing mkdir aborted the suite under `set -e` with NO stderr, which under kcov looked like a coverage-tool failure rather than a PATH failure."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit; install_leechblock.sh returns to a single 485-line file and lib/leechblock_*.sh disappear.",
|
||||
|
||||
@ -127,6 +127,14 @@ WRAP
|
||||
}
|
||||
|
||||
wire_up_browsers() {
|
||||
# The Firefox guidance and the closing summary interpolate the install
|
||||
# layout. Those are set by resolve_version, which the entry script always
|
||||
# runs first; defaulting them keeps this function callable on its own
|
||||
# without aborting under `set -u` on an unbound VERSION_DIR.
|
||||
local version_dir="${VERSION_DIR:-<not yet resolved>}"
|
||||
local version="${VERSION:-<unknown>}"
|
||||
local tag="${TAG:-<unknown>}"
|
||||
|
||||
info "Detecting installed browsers…"
|
||||
for bin in "${!BROWSERS[@]}"; do
|
||||
if command -v "$bin" >/dev/null 2>&1; then
|
||||
@ -158,19 +166,19 @@ Options:
|
||||
https://addons.mozilla.org/firefox/addon/leechblock-ng/
|
||||
2) For testing with Developer Edition or Nightly, you can set xpinstall.signatures.required=false
|
||||
and install a built XPI. We'll still keep the downloaded source at:
|
||||
$VERSION_DIR
|
||||
$version_dir
|
||||
|
||||
To load temporarily for testing (session-only), open 'about:debugging#/runtime/this-firefox' and "Load Temporary Add-on…" then select $VERSION_DIR/manifest.json.
|
||||
To load temporarily for testing (session-only), open 'about:debugging#/runtime/this-firefox' and "Load Temporary Add-on…" then select $version_dir/manifest.json.
|
||||
|
||||
Tip: Re-run this script with --install-firefox to auto-install from AMO via enterprise policy (requires sudo).
|
||||
FF
|
||||
fi
|
||||
|
||||
if [[ $found_any -eq 0 && $ff_found -eq 0 ]]; then
|
||||
warn "No supported browsers detected. We placed the extension at: $VERSION_DIR"
|
||||
warn "No supported browsers detected. We placed the extension at: $version_dir"
|
||||
echo "Supported (auto-wired): ${!BROWSERS[*]}. Detected Firefox variants will show guidance only."
|
||||
fi
|
||||
|
||||
echo
|
||||
info "Done. Version: $VERSION (tag $TAG) installed under $VERSION_DIR"
|
||||
info "Done. Version: $version (tag $tag) installed under $version_dir"
|
||||
}
|
||||
|
||||
@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/leechblock_harness.sh — shared fixture for the install_leechblock.sh split.
|
||||
#
|
||||
# Sourced, not executed. Scope is deliberately narrow: the phases that download
|
||||
# archives, pkill browsers, patch system binaries under sudo and write
|
||||
# /etc/*/policies.json are never executed here — running them would rewrite
|
||||
# this machine's browsers. Those phases are covered by the textual
|
||||
# wrap-verification recorded in the evidence instead.
|
||||
#
|
||||
# What IS executed: the pure logic that decides *what* to do — tag resolution
|
||||
# across its three curl fallbacks, version normalisation, and browser-table
|
||||
# construction. curl/jq are shimmed on PATH so no network call is made.
|
||||
set -euo pipefail
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
_t_pass() {
|
||||
PASS=$((PASS + 1))
|
||||
printf ' OK: %s\n' "$1"
|
||||
}
|
||||
|
||||
_t_fail() {
|
||||
FAIL=$((FAIL + 1))
|
||||
printf ' FAIL: %s\n' "$1"
|
||||
}
|
||||
|
||||
_t_eq() {
|
||||
local want="$1"
|
||||
local got="$2"
|
||||
local what="$3"
|
||||
if [[ $got == "$want" ]]; then
|
||||
_t_pass "$what"
|
||||
else
|
||||
_t_fail "$what (want '${want}', got '${got}')"
|
||||
fi
|
||||
}
|
||||
|
||||
_t_has() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
local what="$3"
|
||||
if [[ $haystack == *"$needle"* ]]; then
|
||||
_t_pass "$what"
|
||||
else
|
||||
_t_fail "$what (want a substring '${needle}')"
|
||||
fi
|
||||
}
|
||||
|
||||
# A function that calls `exit` would kill the test script if invoked directly
|
||||
# in an `if` condition, so failures are asserted inside a subshell.
|
||||
_t_exits_nonzero() {
|
||||
local what="$2"
|
||||
if ("$1" >/dev/null 2>&1); then
|
||||
_t_fail "$what (expected a non-zero exit, got 0)"
|
||||
else
|
||||
_t_pass "$what"
|
||||
fi
|
||||
}
|
||||
|
||||
LEECHBLOCK_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
export LEECHBLOCK_LIB_DIR
|
||||
|
||||
# The libs expect these logging helpers from the entry script.
|
||||
info() { printf '[INFO] %s\n' "$*"; }
|
||||
warn() { printf '[WARN] %s\n' "$*"; }
|
||||
err() { printf '[ERR ] %s\n' "$*"; }
|
||||
|
||||
# Build a throwaway PATH front so `curl`, `jq` and friends are controllable.
|
||||
# Every shim writes what it was asked for into $TEST_TMPDIR so assertions can
|
||||
# inspect the call, and reads its canned reply from a per-test file.
|
||||
_t_setup_shims() {
|
||||
TEST_TMPDIR="$(mktemp -d)"
|
||||
SHIM_DIR="$TEST_TMPDIR/bin"
|
||||
mkdir -p "$SHIM_DIR"
|
||||
|
||||
cat >"$SHIM_DIR/curl" <<'SHIM'
|
||||
#!/usr/bin/env bash
|
||||
# Records argv, then replays a canned body chosen by the caller.
|
||||
printf '%s\n' "$*" >>"$TEST_TMPDIR/curl.calls"
|
||||
if [[ -n ${CURL_FAIL_ALL:-} ]]; then
|
||||
exit 22
|
||||
fi
|
||||
if [[ $* == *"-fsSLI"* ]]; then
|
||||
[[ -f "$TEST_TMPDIR/curl.head" ]] && cat "$TEST_TMPDIR/curl.head"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $* == *"/releases/latest"* ]]; then
|
||||
[[ -f "$TEST_TMPDIR/curl.releases" ]] && cat "$TEST_TMPDIR/curl.releases"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $* == *"/tags"* ]]; then
|
||||
[[ -f "$TEST_TMPDIR/curl.tags" ]] && cat "$TEST_TMPDIR/curl.tags"
|
||||
exit 0
|
||||
fi
|
||||
exit 0
|
||||
SHIM
|
||||
chmod +x "$SHIM_DIR/curl"
|
||||
|
||||
# _t_hide_jq narrows PATH to SHIM_DIR alone, so every external command still
|
||||
# needed on that path must be reachable from here: awk/tr for the
|
||||
# redirect-header fallback, env+bash because the curl shim's own shebang is
|
||||
# `#!/usr/bin/env bash`, and rm/cat/mktemp for the harness itself.
|
||||
# Without these the fallback would fail for the wrong reason and the test
|
||||
# would be asserting nothing.
|
||||
# mkdir is load-bearing: detect_browsers creates the desktop-entry dir, and
|
||||
# a missing mkdir aborts the whole suite under `set -e` with no stderr at
|
||||
# all — which under kcov looks like a coverage-tool problem rather than a
|
||||
# PATH problem. Keep this list in sync with the externals the libs call.
|
||||
local tool
|
||||
for tool in awk tr cat mkdir mktemp rm env bash sed grep wc readlink; do
|
||||
ln -sf "$(command -v "$tool")" "$SHIM_DIR/$tool"
|
||||
done
|
||||
|
||||
export TEST_TMPDIR
|
||||
PATH="$SHIM_DIR:$PATH"
|
||||
export PATH
|
||||
}
|
||||
|
||||
# Narrow PATH to the shim dir alone. Two tests need this and neither can be
|
||||
# written any other way:
|
||||
# * hiding jq — shadowing it with a non-executable shim is NOT enough,
|
||||
# because `command -v` keeps searching PATH and finds the real jq;
|
||||
# * hiding browsers — this machine genuinely has Chromium and Firefox
|
||||
# installed, so "no browser detected" is otherwise untestable.
|
||||
# _t_setup_shims pre-seeds the shim dir with the utilities the code under test
|
||||
# calls, so narrowing hides only what the test means to hide.
|
||||
_t_isolate_path() {
|
||||
_T_SAVED_PATH="$PATH"
|
||||
PATH="$SHIM_DIR"
|
||||
export PATH
|
||||
}
|
||||
|
||||
_t_restore_path() {
|
||||
PATH="$_T_SAVED_PATH"
|
||||
export PATH
|
||||
}
|
||||
|
||||
# Readable aliases for the jq-specific use of the same mechanism.
|
||||
_t_hide_jq() { _t_isolate_path; }
|
||||
_t_show_jq() { _t_restore_path; }
|
||||
|
||||
_t_teardown() {
|
||||
[[ -n ${TEST_TMPDIR:-} && -d ${TEST_TMPDIR} ]] && rm -rf "$TEST_TMPDIR"
|
||||
}
|
||||
|
||||
_t_report() {
|
||||
printf '\n%s: %d passed, %d failed\n' "${1:-tests}" "$PASS" "$FAIL"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs every leechblock lib test. Used directly as the coverage subject:
|
||||
# meta/scripts/shell_coverage.sh wants the test script itself, not `bash <it>`.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
rc=0
|
||||
for t in "$HERE"/test_leechblock_*.sh; do
|
||||
printf '\n=== %s ===\n' "$(basename "$t")"
|
||||
# Each suite exits non-zero on failure; keep going so one red suite does not
|
||||
# hide the state of the others, then fail the run as a whole.
|
||||
# Invoked directly, NOT as `bash "$t"`: under kcov the latter instruments
|
||||
# bash itself and reports zero lines for every lib.
|
||||
"$t" || rc=1
|
||||
done
|
||||
|
||||
exit "$rc"
|
||||
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
# Covers lib/leechblock_browsers.sh's detection table and dispatch logic.
|
||||
#
|
||||
# replace_browser_in_place is NOT executed: it pkills browsers and rewrites
|
||||
# system binaries under sudo. What is asserted here is everything that decides
|
||||
# *whether* it runs and *what* it would be handed — the table itself, the
|
||||
# global-scope contract, and the command-v gating in wire_up_browsers.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=leechblock_harness.sh
|
||||
source "$HERE/leechblock_harness.sh"
|
||||
# shellcheck source=../leechblock_browsers.sh
|
||||
source "$LEECHBLOCK_LIB_DIR/leechblock_browsers.sh"
|
||||
|
||||
_t_setup_shims
|
||||
trap _t_teardown EXIT
|
||||
|
||||
HOME="$TEST_TMPDIR/home"
|
||||
XDG_DATA_HOME="$TEST_TMPDIR/home/.local/share"
|
||||
|
||||
# Assigned by wire_up_browsers in the sourced lib. Declared here so the
|
||||
# assertions below read as checking a known variable rather than tripping
|
||||
# SC2154 on something shellcheck cannot trace across the source boundary.
|
||||
ff_found=0
|
||||
|
||||
printf 'detect_browsers: the tables survive as globals\n'
|
||||
# declare -A inside a function is function-local; the pre-split code ran at top
|
||||
# level. If the -g were ever dropped these counts would be 0 and every browser
|
||||
# would silently go unwired, so assert the count rather than just a lookup.
|
||||
detect_browsers
|
||||
_t_eq "8" "${#BROWSERS[@]}" "all eight Chromium-family browsers are present"
|
||||
_t_eq "3" "${#FIREFOXES[@]}" "all three Firefox-family browsers are present"
|
||||
|
||||
printf '\ndetect_browsers: hyphenated keys resolve\n'
|
||||
# The subscripts are QUOTED deliberately. shfmt -w rewrites an unquoted
|
||||
# ${BROWSERS[google-chrome-stable]} into arithmetic
|
||||
# (the key becomes a subtraction expression), which resolves to the empty key and
|
||||
# makes every one of these assertions read an absent entry. Reproduced in this
|
||||
# very file during the split; quoting is what makes shfmt leave it alone.
|
||||
_t_eq "Google Chrome" "${BROWSERS["google-chrome-stable"]}" "google-chrome-stable maps to its pretty name"
|
||||
_t_eq "Brave" "${BROWSERS["brave-browser"]}" "brave-browser maps to its pretty name"
|
||||
_t_eq "Vivaldi" "${BROWSERS["vivaldi-stable"]}" "vivaldi-stable maps to its pretty name"
|
||||
_t_eq "Thorium" "${BROWSERS["thorium-browser"]}" "thorium-browser maps to its pretty name"
|
||||
_t_eq "Firefox Developer Edition" "${FIREFOXES["firefox-developer-edition"]}" "firefox-developer-edition maps to its pretty name"
|
||||
|
||||
printf '\ndetect_browsers: it creates the user applications directory\n'
|
||||
if [[ -d "$XDG_DATA_HOME/applications" ]]; then
|
||||
_t_pass "the desktop-entry directory is created"
|
||||
else
|
||||
_t_fail "the desktop-entry directory is missing"
|
||||
fi
|
||||
_t_eq "0" "$found_any" "found_any starts at zero"
|
||||
|
||||
printf '\nwire_up_browsers: no installed browser means nothing is patched\n'
|
||||
# This machine really does have Chromium and Firefox installed, so PATH must be
|
||||
# narrowed to the shim dir for "nothing installed" to be testable at all.
|
||||
# _t_setup_shims seeded that dir with the utilities the function needs, so
|
||||
# narrowing hides the browsers without breaking the code under test.
|
||||
_t_isolate_path
|
||||
replace_browser_in_place() {
|
||||
printf 'PATCHED %s\n' "$1" >>"$TEST_TMPDIR/patched"
|
||||
found_any=1
|
||||
}
|
||||
: >"$TEST_TMPDIR/patched"
|
||||
wire_up_browsers >"$TEST_TMPDIR/wire.out" 2>&1
|
||||
_t_restore_path
|
||||
_t_eq "0" "$(wc -l <"$TEST_TMPDIR/patched")" "no browser present means no binary is touched"
|
||||
_t_eq "0" "$ff_found" "no Firefox present leaves ff_found at zero"
|
||||
_t_has "$(cat "$TEST_TMPDIR/wire.out")" "No supported browsers detected" "it reports that nothing was found"
|
||||
|
||||
printf '\nwire_up_browsers: an installed browser is dispatched with its pretty name\n'
|
||||
# SHIM_DIR is first on PATH, so these stand in for a real installed browser.
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' >"$SHIM_DIR/chromium"
|
||||
chmod +x "$SHIM_DIR/chromium"
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' >"$SHIM_DIR/firefox"
|
||||
chmod +x "$SHIM_DIR/firefox"
|
||||
: >"$TEST_TMPDIR/patched"
|
||||
found_any=0
|
||||
wire_up_browsers >"$TEST_TMPDIR/wire2.out" 2>&1
|
||||
_t_has "$(cat "$TEST_TMPDIR/patched")" "PATCHED chromium" "an installed Chromium is handed to the patcher"
|
||||
_t_eq "1" "$ff_found" "an installed Firefox sets ff_found"
|
||||
_t_has "$(cat "$TEST_TMPDIR/wire2.out")" "Detected Firefox-based browser" "it prints the Firefox guidance"
|
||||
|
||||
_t_report "test_leechblock_browsers"
|
||||
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
# Covers lib/leechblock_fetch.sh's tag resolution and version normalisation.
|
||||
#
|
||||
# get_latest_tag has three fallbacks in priority order (releases API → tags
|
||||
# API → redirect-header parse). A silent failure there installs the WRONG
|
||||
# version of a blocking extension, so each fallback is exercised separately,
|
||||
# including the path where jq is absent entirely.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=leechblock_harness.sh
|
||||
source "$HERE/leechblock_harness.sh"
|
||||
# shellcheck source=../leechblock_fetch.sh
|
||||
source "$LEECHBLOCK_LIB_DIR/leechblock_fetch.sh"
|
||||
|
||||
# Exported because get_latest_tag reads them from the environment of the
|
||||
# sourced lib rather than taking them as arguments; a plain assignment reads as
|
||||
# unused here (SC2034) even though the code under test depends on it.
|
||||
export REPO_OWNER="proginosko"
|
||||
export REPO_NAME="LeechBlockNG-chrome"
|
||||
|
||||
_t_setup_shims
|
||||
trap _t_teardown EXIT
|
||||
|
||||
printf 'get_latest_tag: releases API wins when it answers\n'
|
||||
printf '{"tag_name":"v1.2.3"}\n' >"$TEST_TMPDIR/curl.releases"
|
||||
# A subshell would strand nothing here (the value is echoed), but the call is
|
||||
# routed through a file so a future assertion on side effects still works.
|
||||
get_latest_tag "$REPO_NAME" >"$TEST_TMPDIR/out"
|
||||
_t_eq "v1.2.3" "$(cat "$TEST_TMPDIR/out")" "the releases endpoint supplies the tag"
|
||||
_t_has "$(cat "$TEST_TMPDIR/curl.calls")" "releases/latest" "it queried the releases endpoint"
|
||||
|
||||
printf '\nget_latest_tag: falls back to the tags API when releases is empty\n'
|
||||
: >"$TEST_TMPDIR/curl.calls"
|
||||
printf '{}\n' >"$TEST_TMPDIR/curl.releases"
|
||||
printf '[{"name":"v9.9.9"}]\n' >"$TEST_TMPDIR/curl.tags"
|
||||
get_latest_tag "$REPO_NAME" >"$TEST_TMPDIR/out"
|
||||
_t_eq "v9.9.9" "$(cat "$TEST_TMPDIR/out")" "an empty releases reply falls through to tags"
|
||||
_t_has "$(cat "$TEST_TMPDIR/curl.calls")" "tags?per_page=1" "it queried the tags endpoint"
|
||||
|
||||
printf '\nget_latest_tag: a literal null is treated as absent, not as a tag\n'
|
||||
printf '{"tag_name":null}\n' >"$TEST_TMPDIR/curl.releases"
|
||||
printf '[{"name":"v4.5.6"}]\n' >"$TEST_TMPDIR/curl.tags"
|
||||
get_latest_tag "$REPO_NAME" >"$TEST_TMPDIR/out"
|
||||
_t_eq "v4.5.6" "$(cat "$TEST_TMPDIR/out")" "a JSON null does not become the version"
|
||||
|
||||
printf '\nget_latest_tag: redirect-header fallback when jq is unavailable\n'
|
||||
_t_hide_jq
|
||||
printf 'location: https://github.com/x/y/releases/tag/v7.7.7\r\n' >"$TEST_TMPDIR/curl.head"
|
||||
get_latest_tag "$REPO_NAME" >"$TEST_TMPDIR/out"
|
||||
_t_eq "v7.7.7" "$(cat "$TEST_TMPDIR/out")" "the Location header supplies the tag without jq"
|
||||
|
||||
printf '\nget_latest_tag: reports failure when every source is exhausted\n'
|
||||
# Still inside the hidden-jq PATH: with no Location header either, all three
|
||||
# fallbacks are exhausted. The caller aborts the install on this non-zero
|
||||
# status, so returning 0 with an empty tag would install a bogus version.
|
||||
: >"$TEST_TMPDIR/curl.head"
|
||||
if get_latest_tag "$REPO_NAME" >"$TEST_TMPDIR/out" 2>&1; then
|
||||
_t_fail "an exhausted lookup must return non-zero"
|
||||
else
|
||||
_t_pass "an exhausted lookup returns non-zero rather than an empty version"
|
||||
fi
|
||||
_t_show_jq
|
||||
|
||||
printf '\nresolve_version: normalises the tag and derives the install layout\n'
|
||||
VERSION="v2.0.1"
|
||||
HOME="$TEST_TMPDIR/home"
|
||||
XDG_DATA_HOME="$TEST_TMPDIR/home/.local/share"
|
||||
resolve_version
|
||||
_t_eq "2.0.1" "$VERSION" "the leading v is stripped for directory names"
|
||||
_t_eq "v2.0.1" "$TAG" "the tag keeps its leading v"
|
||||
_t_eq "$XDG_DATA_HOME/leechblockng/2.0.1" "$VERSION_DIR" "VERSION_DIR is derived from the stripped version"
|
||||
_t_eq "$XDG_DATA_HOME/leechblockng/current" "$CURRENT_LINK" "CURRENT_LINK is the stable wrapper path"
|
||||
|
||||
printf '\nresolve_version: a non-standard tag warns but still installs\n'
|
||||
VERSION="nightly-build"
|
||||
# warn() prints to stdout, not stderr, so redirect stdout to capture it.
|
||||
resolve_version >"$TEST_TMPDIR/warn" 2>&1
|
||||
_t_has "$(cat "$TEST_TMPDIR/warn")" "doesn't look like" "an odd tag produces a warning"
|
||||
_t_eq "nightly-build" "$VERSION" "an odd tag is still honoured rather than rejected"
|
||||
|
||||
printf '\nresolve_version: an unset VERSION resolves from GitHub\n'
|
||||
printf '{"tag_name":"v3.3.3"}\n' >"$TEST_TMPDIR/curl.releases"
|
||||
VERSION=""
|
||||
resolve_version >/dev/null
|
||||
_t_eq "3.3.3" "$VERSION" "an empty VERSION is filled from the releases endpoint"
|
||||
|
||||
_t_report "test_leechblock_fetch"
|
||||
Loading…
Reference in New Issue
Block a user