package majordomo import ( "context" "encoding/json" "net/http" "testing" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" ) // The contract qwen shares with every other OpenAI-compat built-in (endpoint, // credential isolation, its qwen:// DSN) is asserted by the table in // builtin_openaicompat_test.go. What remains here is qwen-specific: the // reverse-leak direction, and the wire claim ADR-0027 turns on. // TestQwenBuiltinKeyDoesNotLeakToOpenAI: QWEN_API_KEY is the qwen built-in's // credential and nothing else's. Why this direction too: the shared table's // missing-key case only proves qwen never borrows OPENAI_API_KEY; this proves // the reverse — a registry that can see QWEN_API_KEY must not hand it to the // openai built-in, which would send an Alibaba key to api.openai.com. func TestQwenBuiltinKeyDoesNotLeakToOpenAI(t *testing.T) { // Set before newTestRegistry: the openai built-in reads OPENAI_API_KEY at // construction. Giving it a real key is what keeps this test honest — a // keyless openai target would 401 before any request, and the assertion // below would pass without a single byte reaching the wire. t.Setenv("OPENAI_API_KEY", "openai-secret") rt := &captureRT{body: chatCompletionOK} r := newTestRegistry(t, WithEnvLookup(singleKeyEnv("QWEN_API_KEY", "qwen-secret")), WithHTTPClient(&http.Client{Transport: rt}), ) m, err := r.Parse("openai/gpt-4o-mini") if err != nil { t.Fatalf("Parse: %v", err) } if _, err := m.Generate(context.Background(), llm.Request{Messages: []llm.Message{llm.UserText("hi")}}); err != nil { t.Fatalf("Generate: %v", err) } if rt.req == nil { t.Fatal("no request captured") } if want := "Bearer openai-secret"; rt.req.Header.Get("Authorization") != want { t.Errorf("Authorization = %q, want %q — the qwen credential must not reach the openai built-in", rt.req.Header.Get("Authorization"), want) } } // TestQwenReasoningEffortReachesWire is the load-bearing test for ADR-0027's // central claim: Model Studio's OpenAI-compatible surface takes reasoning as a // top-level "reasoning_effort" body field, which the openai client already // sends — so llm.WithReasoningEffort survives the trip on qwen with no // qwen-specific code. Routing qwen through the anthropic client instead would // drop it silently (provider/anthropic ignores ReasoningEffort by design), and // that difference would be invisible without asserting on the wire body. func TestQwenReasoningEffortReachesWire(t *testing.T) { rt := &captureRT{body: chatCompletionOK} r := newTestRegistry(t, WithEnvLookup(singleKeyEnv("QWEN_API_KEY", "qwen-secret")), WithHTTPClient(&http.Client{Transport: rt}), ) m, err := r.Parse("qwen/qwen3.8-max") if err != nil { t.Fatalf("Parse: %v", err) } _, err = m.Generate(context.Background(), llm.Request{ Messages: []llm.Message{llm.UserText("hi")}, ReasoningEffort: "high", }) if err != nil { t.Fatalf("Generate: %v", err) } if rt.reqBody == nil { t.Fatal("no request body captured") } var sent map[string]any if err := json.Unmarshal(rt.reqBody, &sent); err != nil { t.Fatalf("decode request body: %v", err) } if got := sent["reasoning_effort"]; got != "high" { t.Errorf("reasoning_effort = %v, want %q (body: %s)", got, "high", rt.reqBody) } }