@@ -0,0 +1,316 @@
8+ "github.com/charmbracelet/crush/internal/fur/provider"
9+ "github.com/charmbracelet/crush/pkg/env"
10+ "github.com/stretchr/testify/assert"
13+func TestConfig_LoadFromReaders(t *testing.T) {
14+ data1 := strings.NewReader(`{"providers": {"openai": {"api_key": "key1", "base_url": "https://api.openai.com/v1"}}}`)
15+ data2 := strings.NewReader(`{"providers": {"openai": {"api_key": "key2", "base_url": "https://api.openai.com/v2"}}}`)
16+ data3 := strings.NewReader(`{"providers": {"openai": {}}}`)
18+ loadedConfig, err := loadFromReaders([]io.Reader{data1, data2, data3})
20+ assert.NoError(t, err)
21+ assert.NotNil(t, loadedConfig)
22+ assert.Len(t, loadedConfig.Providers, 1)
23+ assert.Equal(t, "key2", loadedConfig.Providers["openai"].APIKey)
24+ assert.Equal(t, "https://api.openai.com/v2", loadedConfig.Providers["openai"].BaseURL)
27+func TestConfig_setDefaults(t *testing.T) {
30+ setDefaults("/tmp", cfg)
32+ assert.NotNil(t, cfg.Options)
33+ assert.NotNil(t, cfg.Options.TUI)
34+ assert.NotNil(t, cfg.Options.ContextPaths)
35+ assert.NotNil(t, cfg.Providers)
36+ assert.NotNil(t, cfg.Models)
37+ assert.NotNil(t, cfg.LSP)
38+ assert.NotNil(t, cfg.MCP)
39+ assert.Equal(t, "/tmp/.crush", cfg.Options.DataDirectory)
40+ for _, path := range defaultContextPaths {
41+ assert.Contains(t, cfg.Options.ContextPaths, path)
43+ assert.Equal(t, "/tmp", cfg.workingDir)
46+func TestConfig_configureProviders(t *testing.T) {
47+ knownProviders := []provider.Provider{
50+ APIKey: "$OPENAI_API_KEY",
51+ APIEndpoint: "https://api.openai.com/v1",
52+ Models: []provider.Model{{
59+ setDefaults("/tmp", cfg)
60+ env := env.NewFromMap(map[string]string{
61+ "OPENAI_API_KEY": "test-key",
63+ resolver := NewEnvironmentVariableResolver(env)
64+ err := configureProviders(cfg, env, resolver, knownProviders)
65+ assert.NoError(t, err)
66+ assert.Len(t, cfg.Providers, 1)
68+ // We want to make sure that we keep the configured API key as a placeholder
69+ assert.Equal(t, "$OPENAI_API_KEY", cfg.Providers["openai"].APIKey)
72+func TestConfig_configureProvidersWithOverride(t *testing.T) {
73+ knownProviders := []provider.Provider{
76+ APIKey: "$OPENAI_API_KEY",
77+ APIEndpoint: "https://api.openai.com/v1",
78+ Models: []provider.Model{{
85+ Providers: map[string]ProviderConfig{
88+ BaseURL: "https://api.openai.com/v2",
89+ Models: []provider.Model{
101+ setDefaults("/tmp", cfg)
102+ env := env.NewFromMap(map[string]string{
103+ "OPENAI_API_KEY": "test-key",
105+ resolver := NewEnvironmentVariableResolver(env)
106+ err := configureProviders(cfg, env, resolver, knownProviders)
107+ assert.NoError(t, err)
108+ assert.Len(t, cfg.Providers, 1)
110+ // We want to make sure that we keep the configured API key as a placeholder
111+ assert.Equal(t, "xyz", cfg.Providers["openai"].APIKey)
112+ assert.Equal(t, "https://api.openai.com/v2", cfg.Providers["openai"].BaseURL)
113+ assert.Len(t, cfg.Providers["openai"].Models, 2)
114+ assert.Equal(t, "Updated", cfg.Providers["openai"].Models[0].Name)
117+func TestConfig_configureProvidersWithNewProvider(t *testing.T) {
118+ knownProviders := []provider.Provider{
121+ APIKey: "$OPENAI_API_KEY",
122+ APIEndpoint: "https://api.openai.com/v1",
123+ Models: []provider.Model{{
130+ Providers: map[string]ProviderConfig{
133+ BaseURL: "https://api.someendpoint.com/v2",
134+ Models: []provider.Model{
142+ setDefaults("/tmp", cfg)
143+ env := env.NewFromMap(map[string]string{
144+ "OPENAI_API_KEY": "test-key",
146+ resolver := NewEnvironmentVariableResolver(env)
147+ err := configureProviders(cfg, env, resolver, knownProviders)
148+ assert.NoError(t, err)
149+ // Should be to because of the env variable
150+ assert.Len(t, cfg.Providers, 2)
152+ // We want to make sure that we keep the configured API key as a placeholder
153+ assert.Equal(t, "xyz", cfg.Providers["custom"].APIKey)
154+ assert.Equal(t, "https://api.someendpoint.com/v2", cfg.Providers["custom"].BaseURL)
155+ assert.Len(t, cfg.Providers["custom"].Models, 1)
157+ _, ok := cfg.Providers["openai"]
158+ assert.True(t, ok, "OpenAI provider should still be present")
161+func TestConfig_configureProvidersBedrockWithCredentials(t *testing.T) {
162+ knownProviders := []provider.Provider{
164+ ID: provider.InferenceProviderBedrock,
167+ Models: []provider.Model{{
168+ ID: "anthropic.claude-sonnet-4-20250514-v1:0",
174+ setDefaults("/tmp", cfg)
175+ env := env.NewFromMap(map[string]string{
176+ "AWS_ACCESS_KEY_ID": "test-key-id",
177+ "AWS_SECRET_ACCESS_KEY": "test-secret-key",
179+ resolver := NewEnvironmentVariableResolver(env)
180+ err := configureProviders(cfg, env, resolver, knownProviders)
181+ assert.NoError(t, err)
182+ assert.Len(t, cfg.Providers, 1)
184+ bedrockProvider, ok := cfg.Providers["bedrock"]
185+ assert.True(t, ok, "Bedrock provider should be present")
186+ assert.Len(t, bedrockProvider.Models, 1)
187+ assert.Equal(t, "anthropic.claude-sonnet-4-20250514-v1:0", bedrockProvider.Models[0].ID)
190+func TestConfig_configureProvidersBedrockWithoutCredentials(t *testing.T) {
191+ knownProviders := []provider.Provider{
193+ ID: provider.InferenceProviderBedrock,
196+ Models: []provider.Model{{
197+ ID: "anthropic.claude-sonnet-4-20250514-v1:0",
203+ setDefaults("/tmp", cfg)
204+ env := env.NewFromMap(map[string]string{})
205+ resolver := NewEnvironmentVariableResolver(env)
206+ err := configureProviders(cfg, env, resolver, knownProviders)
207+ assert.NoError(t, err)
208+ // Provider should not be configured without credentials
209+ assert.Len(t, cfg.Providers, 0)
212+func TestConfig_configureProvidersBedrockWithoutUnsupportedModel(t *testing.T) {
213+ knownProviders := []provider.Provider{
215+ ID: provider.InferenceProviderBedrock,
218+ Models: []provider.Model{{
219+ ID: "some-random-model",
225+ setDefaults("/tmp", cfg)
226+ env := env.NewFromMap(map[string]string{
227+ "AWS_ACCESS_KEY_ID": "test-key-id",
228+ "AWS_SECRET_ACCESS_KEY": "test-secret-key",
230+ resolver := NewEnvironmentVariableResolver(env)
231+ err := configureProviders(cfg, env, resolver, knownProviders)
232+ assert.Error(t, err)
235+func TestConfig_configureProvidersVertexAIWithCredentials(t *testing.T) {
236+ knownProviders := []provider.Provider{
238+ ID: provider.InferenceProviderVertexAI,
241+ Models: []provider.Model{{
248+ setDefaults("/tmp", cfg)
249+ env := env.NewFromMap(map[string]string{
250+ "GOOGLE_GENAI_USE_VERTEXAI": "true",
251+ "GOOGLE_CLOUD_PROJECT": "test-project",
252+ "GOOGLE_CLOUD_LOCATION": "us-central1",
254+ resolver := NewEnvironmentVariableResolver(env)
255+ err := configureProviders(cfg, env, resolver, knownProviders)
256+ assert.NoError(t, err)
257+ assert.Len(t, cfg.Providers, 1)
259+ vertexProvider, ok := cfg.Providers["vertexai"]
260+ assert.True(t, ok, "VertexAI provider should be present")
261+ assert.Len(t, vertexProvider.Models, 1)
262+ assert.Equal(t, "gemini-pro", vertexProvider.Models[0].ID)
263+ assert.Equal(t, "test-project", vertexProvider.ExtraParams["project"])
264+ assert.Equal(t, "us-central1", vertexProvider.ExtraParams["location"])
267+func TestConfig_configureProvidersVertexAIWithoutCredentials(t *testing.T) {
268+ knownProviders := []provider.Provider{
270+ ID: provider.InferenceProviderVertexAI,
273+ Models: []provider.Model{{
280+ setDefaults("/tmp", cfg)
281+ env := env.NewFromMap(map[string]string{
282+ "GOOGLE_GENAI_USE_VERTEXAI": "false",
283+ "GOOGLE_CLOUD_PROJECT": "test-project",
284+ "GOOGLE_CLOUD_LOCATION": "us-central1",
286+ resolver := NewEnvironmentVariableResolver(env)
287+ err := configureProviders(cfg, env, resolver, knownProviders)
288+ assert.NoError(t, err)
289+ // Provider should not be configured without proper credentials
290+ assert.Len(t, cfg.Providers, 0)
293+func TestConfig_configureProvidersVertexAIMissingProject(t *testing.T) {
294+ knownProviders := []provider.Provider{
296+ ID: provider.InferenceProviderVertexAI,
299+ Models: []provider.Model{{
306+ setDefaults("/tmp", cfg)
307+ env := env.NewFromMap(map[string]string{
308+ "GOOGLE_GENAI_USE_VERTEXAI": "true",
309+ "GOOGLE_CLOUD_LOCATION": "us-central1",
311+ resolver := NewEnvironmentVariableResolver(env)
312+ err := configureProviders(cfg, env, resolver, knownProviders)
313+ assert.NoError(t, err)
314+ // Provider should not be configured without project
315+ assert.Len(t, cfg.Providers, 0)