404 lines
8.9 KiB
Go
404 lines
8.9 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLoad(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
configYAML string
|
|
envVars map[string]string
|
|
expectError bool
|
|
validate func(t *testing.T, cfg *Config)
|
|
}{
|
|
{
|
|
name: "basic config with all fields",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: sk-test-key
|
|
anthropic:
|
|
type: anthropic
|
|
api_key: sk-ant-key
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
provider_model_id: gpt-4-turbo
|
|
- name: claude-3
|
|
provider: anthropic
|
|
provider_model_id: claude-3-sonnet-20240229
|
|
auth:
|
|
enabled: true
|
|
issuer: https://accounts.google.com
|
|
audience: my-client-id
|
|
conversations:
|
|
store: memory
|
|
ttl: 1h
|
|
`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, ":8080", cfg.Server.Address)
|
|
assert.Len(t, cfg.Providers, 2)
|
|
assert.Equal(t, "openai", cfg.Providers["openai"].Type)
|
|
assert.Equal(t, "sk-test-key", cfg.Providers["openai"].APIKey)
|
|
assert.Len(t, cfg.Models, 2)
|
|
assert.Equal(t, "gpt-4", cfg.Models[0].Name)
|
|
assert.True(t, cfg.Auth.Enabled)
|
|
assert.Equal(t, "memory", cfg.Conversations.Store)
|
|
},
|
|
},
|
|
{
|
|
name: "config with environment variables",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: ${OPENAI_API_KEY}
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
provider_model_id: gpt-4
|
|
`,
|
|
envVars: map[string]string{
|
|
"OPENAI_API_KEY": "sk-from-env",
|
|
},
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, "sk-from-env", cfg.Providers["openai"].APIKey)
|
|
},
|
|
},
|
|
{
|
|
name: "minimal config",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: test-key
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, ":8080", cfg.Server.Address)
|
|
assert.Len(t, cfg.Providers, 1)
|
|
assert.Len(t, cfg.Models, 1)
|
|
assert.False(t, cfg.Auth.Enabled)
|
|
},
|
|
},
|
|
{
|
|
name: "azure openai provider",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
azure:
|
|
type: azureopenai
|
|
api_key: azure-key
|
|
endpoint: https://my-resource.openai.azure.com
|
|
api_version: "2024-02-15-preview"
|
|
models:
|
|
- name: gpt-4-azure
|
|
provider: azure
|
|
provider_model_id: gpt-4-deployment
|
|
`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, "azureopenai", cfg.Providers["azure"].Type)
|
|
assert.Equal(t, "azure-key", cfg.Providers["azure"].APIKey)
|
|
assert.Equal(t, "https://my-resource.openai.azure.com", cfg.Providers["azure"].Endpoint)
|
|
assert.Equal(t, "2024-02-15-preview", cfg.Providers["azure"].APIVersion)
|
|
},
|
|
},
|
|
{
|
|
name: "vertex ai provider",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
vertex:
|
|
type: vertexai
|
|
project: my-gcp-project
|
|
location: us-central1
|
|
models:
|
|
- name: gemini-pro
|
|
provider: vertex
|
|
provider_model_id: gemini-1.5-pro
|
|
`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, "vertexai", cfg.Providers["vertex"].Type)
|
|
assert.Equal(t, "my-gcp-project", cfg.Providers["vertex"].Project)
|
|
assert.Equal(t, "us-central1", cfg.Providers["vertex"].Location)
|
|
},
|
|
},
|
|
{
|
|
name: "sql conversation store",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: test-key
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
conversations:
|
|
store: sql
|
|
driver: sqlite3
|
|
dsn: conversations.db
|
|
ttl: 2h
|
|
`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, "sql", cfg.Conversations.Store)
|
|
assert.Equal(t, "sqlite3", cfg.Conversations.Driver)
|
|
assert.Equal(t, "conversations.db", cfg.Conversations.DSN)
|
|
assert.Equal(t, "2h", cfg.Conversations.TTL)
|
|
},
|
|
},
|
|
{
|
|
name: "redis conversation store",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: test-key
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
conversations:
|
|
store: redis
|
|
dsn: redis://localhost:6379/0
|
|
ttl: 30m
|
|
`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Equal(t, "redis", cfg.Conversations.Store)
|
|
assert.Equal(t, "redis://localhost:6379/0", cfg.Conversations.DSN)
|
|
assert.Equal(t, "30m", cfg.Conversations.TTL)
|
|
},
|
|
},
|
|
{
|
|
name: "invalid model references unknown provider",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: test-key
|
|
models:
|
|
- name: gpt-4
|
|
provider: unknown_provider
|
|
`,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "invalid YAML",
|
|
configYAML: `invalid: yaml: content: [unclosed`,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "model references provider without required API key",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
`,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "multiple models same provider",
|
|
configYAML: `
|
|
server:
|
|
address: ":8080"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: test-key
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
provider_model_id: gpt-4-turbo
|
|
- name: gpt-3.5
|
|
provider: openai
|
|
provider_model_id: gpt-3.5-turbo
|
|
- name: gpt-4-mini
|
|
provider: openai
|
|
provider_model_id: gpt-4o-mini
|
|
`,
|
|
validate: func(t *testing.T, cfg *Config) {
|
|
assert.Len(t, cfg.Models, 3)
|
|
for _, model := range cfg.Models {
|
|
assert.Equal(t, "openai", model.Provider)
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create temporary config file
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "config.yaml")
|
|
err := os.WriteFile(configPath, []byte(tt.configYAML), 0644)
|
|
require.NoError(t, err, "failed to write test config file")
|
|
|
|
// Set environment variables
|
|
for key, value := range tt.envVars {
|
|
t.Setenv(key, value)
|
|
}
|
|
|
|
// Load config
|
|
cfg, err := Load(configPath)
|
|
|
|
if tt.expectError {
|
|
assert.Error(t, err, "expected an error")
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err, "unexpected error loading config")
|
|
require.NotNil(t, cfg, "config should not be nil")
|
|
|
|
if tt.validate != nil {
|
|
tt.validate(t, cfg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadNonExistentFile(t *testing.T) {
|
|
_, err := Load("/nonexistent/config.yaml")
|
|
assert.Error(t, err, "should error on nonexistent file")
|
|
}
|
|
|
|
func TestConfigValidate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config Config
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid config",
|
|
config: Config{
|
|
Providers: map[string]ProviderEntry{
|
|
"openai": {Type: "openai", APIKey: "test-key"},
|
|
},
|
|
Models: []ModelEntry{
|
|
{Name: "gpt-4", Provider: "openai"},
|
|
},
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "model references unknown provider",
|
|
config: Config{
|
|
Providers: map[string]ProviderEntry{
|
|
"openai": {Type: "openai", APIKey: "test-key"},
|
|
},
|
|
Models: []ModelEntry{
|
|
{Name: "gpt-4", Provider: "unknown"},
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "model references provider without api key",
|
|
config: Config{
|
|
Providers: map[string]ProviderEntry{
|
|
"openai": {Type: "openai"},
|
|
},
|
|
Models: []ModelEntry{
|
|
{Name: "gpt-4", Provider: "openai"},
|
|
},
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "no models",
|
|
config: Config{
|
|
Providers: map[string]ProviderEntry{
|
|
"openai": {Type: "openai"},
|
|
},
|
|
Models: []ModelEntry{},
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "multiple models multiple providers",
|
|
config: Config{
|
|
Providers: map[string]ProviderEntry{
|
|
"openai": {Type: "openai", APIKey: "test-key"},
|
|
"anthropic": {Type: "anthropic", APIKey: "ant-key"},
|
|
},
|
|
Models: []ModelEntry{
|
|
{Name: "gpt-4", Provider: "openai"},
|
|
{Name: "claude-3", Provider: "anthropic"},
|
|
},
|
|
},
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.config.validate()
|
|
if tt.expectError {
|
|
assert.Error(t, err, "expected validation error")
|
|
} else {
|
|
assert.NoError(t, err, "unexpected validation error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnvironmentVariableExpansion(t *testing.T) {
|
|
configYAML := `
|
|
server:
|
|
address: "${SERVER_ADDRESS}"
|
|
providers:
|
|
openai:
|
|
type: openai
|
|
api_key: ${OPENAI_KEY}
|
|
anthropic:
|
|
type: anthropic
|
|
api_key: ${ANTHROPIC_KEY:-default-key}
|
|
models:
|
|
- name: gpt-4
|
|
provider: openai
|
|
`
|
|
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "config.yaml")
|
|
err := os.WriteFile(configPath, []byte(configYAML), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Set only some env vars to test defaults
|
|
t.Setenv("SERVER_ADDRESS", ":9090")
|
|
t.Setenv("OPENAI_KEY", "sk-from-env")
|
|
// Don't set ANTHROPIC_KEY to test default value
|
|
|
|
cfg, err := Load(configPath)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, ":9090", cfg.Server.Address)
|
|
assert.Equal(t, "sk-from-env", cfg.Providers["openai"].APIKey)
|
|
// Note: Go's os.Expand doesn't support default values like ${VAR:-default}
|
|
// This is just documenting current behavior
|
|
}
|