Files
gadfly/scripts/preflight_test.sh
T
steveandClaude Opus 5 67a73616e1
Build & push image / build-and-push (pull_request) Successful in 5s
Build & push image / test (pull_request) Canceled after 7m27s
fix(qwen): gadfly round 3 — stop guarding a duplicate, delete it
Twelve findings, all real, and the two that matter are about the pre-flight I
added rather than about qwen.

The credential check had a false pass in the OTHER direction from round 2's: on
the GADFLY_BASE_URL override path, resolveModel builds the client with
GADFLY_API_KEY and never reads QWEN_API_KEY/KIMI_API_KEY, so treating the
provider's own key as sufficient there let a doomed run proceed. Having now
been wrong about these rules in both directions, the check no longer tries to
model both paths: it covers the REGISTRY path, whose rules it can state
exactly, and says nothing about the override path — which is hand-configured by
definition, while the registry path is the one you hit by adding a model id to
a var and forgetting the secret.

The logic moves to scripts/preflight.sh, sourced by both run.sh and the test.
The previous answer to "this test duplicates production logic" was a regex
drift-guard, and that guard compared only the provider table — not the decision
logic, which is precisely the half that carried the bug. A duplicate you guard
is still a duplicate; this deletes it, and the test now runs under `set -u`
like production does.

Also: the test that pins the shared provider slice held its own copy of the
list (now ranges the slice); endpointProviderNames had nothing tying it to the
switches it describes, which is how it shipped without "gemini" (a new test
asserts every advertised name resolves); two godoc lists had drifted; and the
"sanity" line that asserted nothing is gone.

And the repo had NO test job — `go test` and the pre-flight table both existed
and neither was ever executed by CI, which reads as coverage while providing
none. Added one (build/vet/gofmt/test/pre-flight), running alongside the image
build rather than gating it, so red is loud without standing between a push and
a rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-12 17:20:44 -04:00

89 lines
4.0 KiB
Bash

#!/usr/bin/env bash
# Table test for the credential pre-flight in preflight.sh.
#
# It SOURCES the real implementation rather than copying it. An earlier version
# duplicated the logic and reconciled the copies with a regex diff — which only
# covered the provider table and not the decision logic, i.e. exactly the half
# that had the bug. Sourcing removes the second copy entirely.
#
# Run: scripts/preflight_test.sh (exit 0 = all cases pass)
set -u
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=preflight.sh
. "$SCRIPT_DIR/preflight.sh"
fail=0
check() { # description, want, got
if [ "$2" = "$3" ]; then
echo "ok $1"
else
echo "FAIL $1 — want '$2', got '$3'"
fail=1
fi
}
# probe <provider> [VAR=VAL ...] — run the real function in a clean environment
# under the same shell options production uses (set -u), so an unset-variable
# bug surfaces here instead of in a live review.
probe() {
local provider="$1"; shift
env -i PATH="$PATH" HOME="$HOME" "$@" bash -c "
set -u
. '$SCRIPT_DIR/preflight.sh'
gadfly_preflight_key '$provider'
"
}
echo "== registry path: keyed providers with no key must name their variable =="
check "qwen, no key" "QWEN_API_KEY" "$(probe qwen)"
check "kimi, no key" "KIMI_API_KEY" "$(probe kimi)"
check "ollama-cloud, no key" "OLLAMA_CLOUD_API_KEY" "$(probe ollama-cloud)"
check "openai, no key" "OPENAI_API_KEY" "$(probe openai)"
check "openai-compatible, none" "OPENAI_API_KEY" "$(probe openai-compatible)"
check "anthropic, no key" "ANTHROPIC_API_KEY" "$(probe anthropic)"
echo "== registry path: the provider's own key lets it run =="
check "qwen, keyed" "" "$(probe qwen QWEN_API_KEY=k)"
check "kimi, keyed" "" "$(probe kimi KIMI_API_KEY=k)"
check "ollama-cloud, keyed" "" "$(probe ollama-cloud OLLAMA_API_KEY=k)"
check "openai-compatible, keyed" "" "$(probe openai-compatible OPENAI_API_KEY=k)"
echo "== a wrong-provider key never satisfies a provider (no cross-fallback) =="
check "qwen w/ only OPENAI key" "QWEN_API_KEY" "$(probe qwen OPENAI_API_KEY=k)"
check "kimi w/ only QWEN key" "KIMI_API_KEY" "$(probe kimi QWEN_API_KEY=k)"
echo "== an empty-string key counts as missing, not present =="
check "qwen, empty key" "QWEN_API_KEY" "$(probe qwen QWEN_API_KEY=)"
echo "== GADFLY_API_KEY does NOT substitute on the registry path =="
# resolveModel reads GADFLY_API_KEY only after its `baseURL == ""` early
# return, so on this path the built-in reads its own variable and a set
# GADFLY_API_KEY changes nothing. Treating it as sufficient was a false pass.
check "qwen w/ GADFLY_API_KEY only" "QWEN_API_KEY" "$(probe qwen GADFLY_API_KEY=k)"
echo "== override path (GADFLY_BASE_URL set) is deliberately not pre-flighted =="
# The credential there is GADFLY_API_KEY with a client-specific fallback, and
# the built-ins' own variables are never read. Checking one path's rules
# against the other produced a false pass in BOTH directions, so this path is
# left alone rather than guessed at.
check "qwen + BASE_URL, no keys" "" "$(probe qwen GADFLY_BASE_URL=https://x)"
check "qwen + BASE_URL + own key" "" "$(probe qwen GADFLY_BASE_URL=https://x QWEN_API_KEY=k)"
check "qwen + BASE_URL + GADFLY key" "" "$(probe qwen GADFLY_BASE_URL=https://x GADFLY_API_KEY=k)"
check "openai + BASE_URL, no keys" "" "$(probe openai GADFLY_BASE_URL=https://x)"
echo "== providers needing no key are never blocked, with nothing set =="
for p in ollama llama-swap llama-swaps llamaswap llamaswaps foreman google gemini some-dsn-name; do
check "unkeyed $p" "" "$(probe "$p")"
done
# google is absent from the table on purpose: it accepts GOOGLE_API_KEY *or*
# GEMINI_API_KEY, so a one-name arm would skip a correctly-configured run.
check "google w/ only GEMINI_API_KEY" "" "$(probe google GEMINI_API_KEY=k)"
if [ "$fail" -ne 0 ]; then
echo "RESULT: preflight table FAILED"
exit 1
fi
echo "RESULT: all pre-flight cases pass"