fix(qwen): gadfly round 3 — stop guarding a duplicate, delete it
Build & push image / build-and-push (pull_request) Successful in 5s
Build & push image / test (pull_request) Canceled after 7m27s

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]>
This commit is contained in:
2026-08-12 17:20:44 -04:00
co-authored by Claude Opus 5
parent 1d6eaa08c5
commit 67a73616e1
6 changed files with 212 additions and 119 deletions
+26 -2
View File
@@ -1,6 +1,9 @@
package main
import "testing"
import (
"strings"
"testing"
)
func TestEndpointProvider(t *testing.T) {
t.Run("ollama http endpoint registers under its name", func(t *testing.T) {
@@ -77,7 +80,9 @@ func TestEndpointProvider(t *testing.T) {
// reason a user could guess — which is exactly what happened here on the first
// pass. Asserting both in one table is what makes the pair fail together.
func TestOpenAICompatProvidersResolveOnBothPaths(t *testing.T) {
for _, provider := range []string{"openai", "openai-compatible", "kimi", "qwen"} {
// Ranges the SHARED slice rather than a fourth copy of the names: a test
// that pins a list against drift must not be able to drift from it.
for _, provider := range openAICompatProviders {
t.Run(provider+" via GADFLY_ENDPOINT_*", func(t *testing.T) {
p, err := endpointProvider("ep", provider+"|https://host.example/v1|sk-x")
if err != nil {
@@ -99,6 +104,25 @@ func TestOpenAICompatProvidersResolveOnBothPaths(t *testing.T) {
}
}
// TestEndpointProviderNamesAreAllAccepted keeps the operator-facing list
// honest. endpointProviderNames exists to stop two error messages drifting
// apart, but nothing tied it to the switches it describes — and its first
// version had already dropped the "gemini" alias, so the anti-drift list was
// itself drifted. Every name it advertises must actually resolve.
func TestEndpointProviderNamesAreAllAccepted(t *testing.T) {
for _, name := range strings.Split(endpointProviderNames, "/") {
name = strings.TrimSpace(name)
if name == "" {
continue
}
t.Run(name, func(t *testing.T) {
if _, err := endpointProvider("ep", name+"|https://host.example/v1|sk-x"); err != nil {
t.Errorf("endpointProviderNames advertises %q but endpointProvider rejects it: %v", name, err)
}
})
}
}
func TestBuildSpec(t *testing.T) {
tests := []struct {
name string