Document optional API key environment behavior

This commit is contained in:
2026-08-25 00:44:26 +00:00
parent a11c80291e
commit 2d44305a8a
9 changed files with 111 additions and 48 deletions

View File

@@ -977,40 +977,72 @@ func TestPrepareDirectAPIKeyBypassesMissingEnvWithoutLeakingOrHashing(t *testing
}
}
func TestMissingCredentialsFailClearlyWhenProfileRequiresAuth(t *testing.T) {
func TestOptionalMissingCredentialsReachUpstream(t *testing.T) {
const missingEnv = "PROMPTKIT_PUBLIC_AUTH_MISSING"
const providerBody = `{"error":{"message":"authentication failed","type":"authentication_error","code":"invalid_api_key"}}`
t.Setenv(missingEnv, "")
engine, err := promptkit.NewEngine(promptkit.Config{
called := false
config := promptkit.Config{
PromptDir: frameworkPromptDir,
SchemaDir: frameworkSchemaDir,
}, promptkit.WithProfiles(promptkit.Profile{
ID: "requires-auth",
Endpoint: "http://localhost:8000/v1",
Model: "test-model",
APIKeyRequired: true,
}))
HTTPClient: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
called = true
if values := req.Header.Values("Authorization"); len(values) != 0 {
t.Fatalf("Authorization values = %q, want absent", values)
}
return &http.Response{
StatusCode: http.StatusUnauthorized,
ContentLength: int64(len(providerBody)),
Body: io.NopCloser(strings.NewReader(providerBody)),
}, nil
})},
}
engine, err := promptkit.NewEngine(config,
promptkit.WithBackend(promptkit.Backend{
ID: "optional-auth",
Endpoint: "http://provider.test/v1",
APIKeyEnv: missingEnv,
}),
promptkit.WithProfiles(promptkit.Profile{
ID: "optional-auth-profile",
BackendID: "optional-auth",
Model: "test-model",
}),
)
if err != nil {
t.Fatalf("expected engine construction to succeed, got %v", err)
}
_, err = engine.Prepare(context.Background(), promptkit.RunRequest{
result, err := engine.Run(context.Background(), promptkit.RunRequest{
PromptID: frameworkMarkdownSummaryPromptID,
ProfileID: "requires-auth",
Execution: &promptkit.ExecutionTargetOverride{APIKeyEnv: missingEnv},
ProfileID: "optional-auth-profile",
Inputs: map[string]promptkit.ArtifactRef{
"transcript": promptkit.Inline("Rin opens the gate."),
"glossary": promptkit.Inline("gate: A guarded passage."),
},
})
if !errors.Is(err, promptkit.ErrInvalidRequest) {
t.Fatalf("expected invalid request for missing credentials, got %v", err)
if !called {
t.Fatal("optional missing credential did not reach upstream")
}
if !errors.Is(err, promptkit.ErrAPIKeyEnvMissing) {
t.Fatalf("expected missing credential environment error, got %v", err)
if result != nil {
t.Fatalf("result = %+v, want nil", result)
}
if err == nil || !strings.Contains(err.Error(), missingEnv) {
t.Fatalf("expected missing env name in error, got %v", err)
if errors.Is(err, promptkit.ErrInvalidRequest) || errors.Is(err, promptkit.ErrAPIKeyEnvMissing) {
t.Fatalf("error = %v, want upstream generation error without credential identities", err)
}
if !errors.Is(err, promptkit.ErrLLMGenerate) {
t.Fatalf("error = %v, want ErrLLMGenerate", err)
}
var generationErr *promptkit.GenerationError
if !errors.As(err, &generationErr) {
t.Fatalf("error = %v, want GenerationError", err)
}
if generationErr.StatusCode() != http.StatusUnauthorized ||
generationErr.ProviderType() != "authentication_error" ||
generationErr.ProviderCode() != "invalid_api_key" ||
generationErr.ProviderMessage() != "authentication failed" {
t.Fatalf("GenerationError = %+v, want structured upstream authentication failure", generationErr)
}
}