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) } } 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") } }