diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index 98415b1..0be4a3e 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -31,6 +31,7 @@ on: - "tests/external-installer.sh" - "tests/forge-version-key.sh" - "tests/forge-connection.sh" + - "tests/forge-release-json.sh" - ".github/workflows/verify-split-files.yml" push: branches: [main] @@ -53,6 +54,7 @@ on: - "tests/external-installer.sh" - "tests/forge-version-key.sh" - "tests/forge-connection.sh" + - "tests/forge-release-json.sh" - ".github/workflows/verify-split-files.yml" workflow_dispatch: @@ -101,6 +103,9 @@ jobs: - name: Forge connection diagnosis tests run: bash tests/forge-connection.sh + - name: Forge release lookup tests + run: bash tests/forge-release-json.sh + - name: Prefetch lists cover every engine file run: | set -euo pipefail diff --git a/lib/forge.func b/lib/forge.func index 0fced73..601e284 100644 --- a/lib/forge.func +++ b/lib/forge.func @@ -1481,6 +1481,9 @@ _forge_release_json() { local forge="${1:?forge}" repo="${2:?repo}" version="${3:-latest}" local tag_prefix="${4:-}" out="${5:?outfile}" + # The install path arrives here without check_for_release; a Proxmox host has no jq. + ensure_dependencies jq || return 100 + local base repo_path list one base="$(_forge_descriptor "$forge" base)" repo_path="$repo" @@ -1591,11 +1594,15 @@ _forge_release_json() { if [[ "$ok" != "true" ]]; then if [[ "$code" == "000" ]]; then - rm -f "$raw" if ((curl_rc == 0)); then + local excerpt + excerpt="$(head -c 160 "$raw" | tr -cd '[:print:]')" + rm -f "$raw" msg_error "${forge} answered, but not with JSON: a captive portal or a proxy error page is in the way" + msg_error "The response starts with: ${excerpt:-(empty)}" return 22 fi + rm -f "$raw" msg_error "Connection to ${forge} failed" _forge_explain_failure "$host" return "$curl_rc" diff --git a/tests/forge-release-json.sh b/tests/forge-release-json.sh new file mode 100644 index 0000000..e58aed1 --- /dev/null +++ b/tests/forge-release-json.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -uo pipefail + +CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +eval "$(sed -n '/^_FORGE_JQ_VKEY_DEF=/,/^_FORGE_JQ_VKEY=/p' "$CORE_DIR/lib/forge.func")" +eval "$(awk '/^_forge_(descriptor|curl|designated_release|stable_filter|keep_raw|release_json|explain_failure)[(][)] [{]/,/^}/' "$CORE_DIR/lib/forge.func")" + +TEST_DIR="$(mktemp -d)" +trap 'rm -rf -- "$TEST_DIR" "${TOOLS_GH_REL_JSON:-}"' EXIT +CALLS="$TEST_DIR/calls" +LOG="$TEST_DIR/log" +OUT="$TEST_DIR/out" +passed=0 failed=0 + +msg_info() { echo "info: $*" >>"$LOG"; } +msg_ok() { echo "ok: $*" >>"$LOG"; } +msg_warn() { echo "warn: $*" >>"$LOG"; } +msg_error() { echo "error: $*" >>"$LOG"; } +getent() { return 0; } +_github_release_noapi() { return 1; } +ensure_dependencies() { + echo "ensure_dependencies $*" >>"$CALLS" + return "${MOCK_DEPS_RC:-0}" +} +# /releases/latest answers per MOCK_LATEST_*, every other URL per MOCK_*. +curl() { + local out="" url="" i + local -a args=("$@") + for ((i = 0; i < ${#args[@]}; i++)); do + case "${args[$i]}" in + -o) out="${args[$((i + 1))]}" ;; + https://*) url="${args[$i]}" ;; + esac + done + echo "curl $url" >>"$CALLS" + if [[ "$url" == */releases/latest ]]; then + printf '%s' "${MOCK_LATEST_BODY:-null}" >"$out" + printf '%s' "${MOCK_LATEST_CODE:-404}" + return 0 + fi + printf '%s' "${MOCK_BODY:-}" >"$out" + printf '%s' "${MOCK_CODE:-200}" + return "${MOCK_RC:-0}" +} + +run() { + : >"$CALLS" + : >"$LOG" + : >"$OUT" + _forge_release_json github filebrowser/filebrowser latest "" "$OUT" +} +line_of() { grep -nF -- "$1" "$CALLS" | head -n1 | cut -d: -f1; } +expect() { + if [[ "$2" == "$3" ]]; then + echo "PASS $1" + passed=$((passed + 1)) + else + echo "FAIL $1: expected <$2>, got <$3>" >&2 + sed 's/^/ /' "$LOG" >&2 + failed=$((failed + 1)) + fi +} + +MOCK_CODE=200 MOCK_BODY='Blocked by proxy' run +expect "an HTML page is reported as not JSON" 22 "$?" +expect "the message names the body" 1 "$(grep -c 'The response starts with: Blocked by proxy' "$LOG")" +expect "jq is ensured before the first request" yes "$( (($(line_of ensure_dependencies) < $(line_of curl))) && echo yes || echo no)" + +MOCK_CODE=200 MOCK_BODY='' run +expect "an empty body is reported as not JSON" 22 "$?" +expect "the message says the body was empty" 1 "$(grep -c 'The response starts with: (empty)' "$LOG")" + +MOCK_DEPS_RC=1 run +expect "a missing jq stops the lookup" 100 "$?" +expect "nothing is fetched without jq" 0 "$(grep -c curl "$CALLS")" + +MOCK_BODY='[{"tag_name":"v1.0.0","draft":false,"prerelease":false},{"tag_name":"v2.0.0-rc.1","draft":false,"prerelease":true}]' run +expect "the release list still resolves" 0 "$?" +expect "the stable release wins" v1.0.0 "$(jq -r .tag_name "$OUT")" + +MOCK_LATEST_CODE=200 MOCK_LATEST_BODY='{"tag_name":"v3.0.0","draft":false,"prerelease":false}' run +expect "the designated release still resolves" 0 "$?" +expect "the designated release is used" v3.0.0 "$(jq -r .tag_name "$OUT")" +expect "only one request was needed" 1 "$(grep -c curl "$CALLS")" + +printf '%s passed, %s failed\n' "$passed" "$failed" +((failed == 0))