Files
descrybe/apps/api/internal/platformsettings/ai_configs_test.go
T

196 lines
5.4 KiB
Go
Raw Normal View History

package platformsettings
import (
"context"
"strings"
"testing"
)
func TestValidAIRole(t *testing.T) {
for _, role := range AIRoles {
if !ValidAIRole(role) {
t.Fatalf("%q should be valid", role)
}
}
if ValidAIRole("embeddings") {
t.Fatal("embeddings alias is not a stored role key")
}
if ValidAIRole("") {
t.Fatal("empty role should be invalid")
}
}
func TestPublicAIConfigs_masksSecret(t *testing.T) {
key := DeriveKey("test-ai-config-secret-material", "fallback")
plain := "sk-live-super-secret-key"
enc, err := EncryptSecret(key, plain)
if err != nil {
t.Fatal(err)
}
svc := &Service{Key: key}
doc := storedDoc{
AIConfigs: map[string]aiConfigStored{
AIRoleSupport: {
Provider: "openai",
BaseURL: "https://api.openai.com/v1",
Model: "gpt-4o-mini",
APIKeyEnc: enc,
APIKeyLast4: last4(plain),
Enabled: true,
Extras: map[string]string{"temperature": "0.2"},
},
},
}
view := svc.publicAIConfigs(doc)
if len(view) != len(AIRoles) {
t.Fatalf("expected %d roles, got %d", len(AIRoles), len(view))
}
got := view[AIRoleSupport]
if got.APIKeyMasked == "" || strings.Contains(got.APIKeyMasked, "super-secret") {
t.Fatalf("api key not masked: %+v", got)
}
if got.HasAPIKey != true || got.APIKeyLast4 != last4(plain) {
t.Fatalf("unexpected mask meta: %+v", got)
}
if got.Extras["temperature"] != "0.2" {
t.Fatalf("extras: %+v", got.Extras)
}
if view[AIRoleDocsAPI].Role != AIRoleDocsAPI {
t.Fatalf("missing empty role stub: %+v", view[AIRoleDocsAPI])
}
}
func TestPublicAIConfigs_processingFallsBackToOpenAI(t *testing.T) {
key := DeriveKey("test-ai-config-secret-material", "fallback")
plain := "sk-legacy-abcdef12"
enc, err := EncryptSecret(key, plain)
if err != nil {
t.Fatal(err)
}
svc := &Service{Key: key}
doc := storedDoc{
OpenAI: openaiStored{
BaseURL: "https://example.test/v1",
Model: "gpt-test",
APIKeyEnc: enc,
APIKeyLast4: last4(plain),
},
}
view := svc.publicAIConfigs(doc)
got := view[AIRoleProcessing]
if !got.HasAPIKey || got.Source != SourceDB || got.Model != "gpt-test" {
t.Fatalf("processing fallback: %+v", got)
}
if strings.Contains(got.APIKeyMasked, "legacy") {
t.Fatalf("leaked key: %q", got.APIKeyMasked)
}
}
func TestResolveAIConfig_envFallback(t *testing.T) {
svc := NewService(nil, EnvConfig{
OpenAIAPIKey: "env-key-1234",
OpenAIBaseURL: "https://api.openai.com/v1",
OpenAIModel: "gpt-4o",
})
got, err := svc.ResolveAIConfig(context.Background(), AIRoleProcessing)
if err != nil {
t.Fatal(err)
}
if got.APIKey != "env-key-1234" || got.Source != SourceEnv || !got.Enabled {
t.Fatalf("got %+v", got)
}
support, err := svc.ResolveAIConfig(context.Background(), AIRoleSupport)
if err != nil {
t.Fatal(err)
}
if support.APIKey != "" || support.Source != SourceNone {
t.Fatalf("support should not use openai env: %+v", support)
}
vec, err := svc.ResolveAIConfig(context.Background(), AIRoleVectorization)
if err != nil {
t.Fatal(err)
}
if vec.APIKey != "env-key-1234" || vec.Source != SourceEnv || vec.Model == "" {
t.Fatalf("vectorization env fallback: %+v", vec)
}
}
2026-08-16 16:57:36 +02:00
func TestResolveAIConfig_dbPreferredOverMockEnv(t *testing.T) {
key := DeriveKey("test-ai-config-secret-material", "fallback")
plain := "sk-db-overloaded-key"
enc, err := EncryptSecret(key, plain)
if err != nil {
t.Fatal(err)
}
svc := &Service{
Key: key,
Env: EnvConfig{
OpenAIAPIKey: "sk-mock-env",
OpenAIBaseURL: "http://127.0.0.1:18767/v1",
OpenAIModel: "mock-llm",
},
}
doc := storedDoc{
OpenAI: openaiStored{
BaseURL: "https://llm.overloadedbot.org/v1",
Model: "code-fast",
APIKeyEnc: enc,
APIKeyLast4: last4(plain),
},
}
got, err := svc.resolveLegacyOpenAI(doc)
if err != nil {
t.Fatal(err)
}
if got.Source != SourceDB || got.APIKey != plain || got.BaseURL != "https://llm.overloadedbot.org/v1" || got.Model != "code-fast" {
t.Fatalf("legacy resolve should prefer DB over mock env: %+v", got)
}
}
func TestPatchAIExtras(t *testing.T) {
var extras map[string]string
val := "1536"
if err := patchAIExtras(&extras, map[string]*string{"dimensions": &val}); err != nil {
t.Fatal(err)
}
if extras["dimensions"] != "1536" {
t.Fatalf("got %#v", extras)
}
if err := patchAIExtras(&extras, map[string]*string{"dimensions": nil}); err != nil {
t.Fatal(err)
}
if extras != nil {
t.Fatalf("expected nil after delete, got %#v", extras)
}
if err := patchAIExtras(&extras, map[string]*string{"": &val}); err == nil {
t.Fatal("expected empty key error")
}
if err := patchAIExtras(&extras, map[string]*string{"bad key": &val}); err == nil {
t.Fatal("expected whitespace key error")
}
}
func TestPatchAIConfig_keepsSecretWhenOmitted(t *testing.T) {
key := DeriveKey("test-ai-config-secret-material", "fallback")
enc, err := EncryptSecret(key, "keep-me-secret")
if err != nil {
t.Fatal(err)
}
svc := &Service{Key: key}
st := aiConfigStored{APIKeyEnc: enc, APIKeyLast4: last4("keep-me-secret"), Provider: "openai"}
model := "new-model"
if err := svc.patchAIConfig(&st, AIConfigUpdate{Model: &model}); err != nil {
t.Fatal(err)
}
if st.APIKeyEnc != enc || st.Model != "new-model" {
t.Fatalf("unexpected state: %+v", st)
}
empty := ""
if err := svc.patchAIConfig(&st, AIConfigUpdate{APIKey: &empty}); err != nil {
t.Fatal(err)
}
if st.APIKeyEnc != enc {
t.Fatal("empty api_key should keep existing")
}
}