@@ -5,7 +5,6 @@ import (
7 "path/filepath"
7 "path/filepath"
11 "github.com/charmbracelet/catwalk/pkg/catwalk"
10 "github.com/charmbracelet/catwalk/pkg/catwalk"
@@ -22,12 +21,12 @@ func TestMain(m *testing.M) {
22 os.Exit(exitVal)
21 os.Exit(exitVal)
25−func TestConfig_LoadFromReaders(t *testing.T) {
24+func TestConfig_LoadFromBytes(t *testing.T) {
26− data1 := strings.NewReader(`{"providers": {"openai": {"api_key": "key1", "base_url": "https://api.openai.com/v1"}}}`)
25+ data1 := []byte(`{"providers": {"openai": {"api_key": "key1", "base_url": "https://api.openai.com/v1"}}}`)
27− data2 := strings.NewReader(`{"providers": {"openai": {"api_key": "key2", "base_url": "https://api.openai.com/v2"}}}`)
26+ data2 := []byte(`{"providers": {"openai": {"api_key": "key2", "base_url": "https://api.openai.com/v2"}}}`)
28− data3 := strings.NewReader(`{"providers": {"openai": {}}}`)
27+ data3 := []byte(`{"providers": {"openai": {}}}`)
30− loadedConfig, err := loadFromReaders([]io.Reader{data1, data2, data3})
29+ loadedConfig, err := loadFromBytes([][]byte{data1, data2, data3})
32 require.NoError(t, err)
31 require.NoError(t, err)
33 require.NotNil(t, loadedConfig)
32 require.NotNil(t, loadedConfig)
@@ -1095,6 +1094,217 @@ func TestConfig_defaultModelSelection(t *testing.T) {
1097+func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
1098+ t.Run("when enabled, ignores all default providers and requires full specification", func(t *testing.T) {
1099+ knownProviders := []catwalk.Provider{
1102+ APIKey: "$OPENAI_API_KEY",
1103+ APIEndpoint: "https://api.openai.com/v1",
1104+ Models: []catwalk.Model{{
1110+ // User references openai but doesn't fully specify it (no base_url, no
1111+ // models). This should be rejected because disable_default_providers
1112+ // treats all providers as custom.
1115+ DisableDefaultProviders: true,
1117+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1119+ APIKey: "$OPENAI_API_KEY",
1123+ cfg.setDefaults("/tmp", "")
1125+ env := env.NewFromMap(map[string]string{
1126+ "OPENAI_API_KEY": "test-key",
1128+ resolver := NewEnvironmentVariableResolver(env)
1129+ err := cfg.configureProviders(env, resolver, knownProviders)
1130+ require.NoError(t, err)
1132+ // openai should NOT be present because it lacks base_url and models.
1133+ require.Equal(t, 0, cfg.Providers.Len())
1134+ _, exists := cfg.Providers.Get("openai")
1135+ require.False(t, exists, "openai should not be present without full specification")
1138+ t.Run("when enabled, fully specified providers work", func(t *testing.T) {
1139+ knownProviders := []catwalk.Provider{
1142+ APIKey: "$OPENAI_API_KEY",
1143+ APIEndpoint: "https://api.openai.com/v1",
1144+ Models: []catwalk.Model{{
1150+ // User fully specifies their provider.
1153+ DisableDefaultProviders: true,
1155+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1157+ APIKey: "$MY_API_KEY",
1158+ BaseURL: "https://my-llm.example.com/v1",
1159+ Models: []catwalk.Model{{
1165+ cfg.setDefaults("/tmp", "")
1167+ env := env.NewFromMap(map[string]string{
1168+ "MY_API_KEY": "test-key",
1169+ "OPENAI_API_KEY": "test-key",
1171+ resolver := NewEnvironmentVariableResolver(env)
1172+ err := cfg.configureProviders(env, resolver, knownProviders)
1173+ require.NoError(t, err)
1175+ // Only fully specified provider should be present.
1176+ require.Equal(t, 1, cfg.Providers.Len())
1177+ provider, exists := cfg.Providers.Get("my-llm")
1178+ require.True(t, exists, "my-llm should be present")
1179+ require.Equal(t, "https://my-llm.example.com/v1", provider.BaseURL)
1180+ require.Len(t, provider.Models, 1)
1182+ // Default openai should NOT be present.
1183+ _, exists = cfg.Providers.Get("openai")
1184+ require.False(t, exists, "openai should not be present")
1187+ t.Run("when disabled, includes all known providers with valid credentials", func(t *testing.T) {
1188+ knownProviders := []catwalk.Provider{
1191+ APIKey: "$OPENAI_API_KEY",
1192+ APIEndpoint: "https://api.openai.com/v1",
1193+ Models: []catwalk.Model{{
1199+ APIKey: "$ANTHROPIC_API_KEY",
1200+ APIEndpoint: "https://api.anthropic.com/v1",
1201+ Models: []catwalk.Model{{
1207+ // User only configures openai, both API keys are available, but option
1211+ DisableDefaultProviders: false,
1213+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1215+ APIKey: "$OPENAI_API_KEY",
1219+ cfg.setDefaults("/tmp", "")
1221+ env := env.NewFromMap(map[string]string{
1222+ "OPENAI_API_KEY": "test-key",
1223+ "ANTHROPIC_API_KEY": "test-key",
1225+ resolver := NewEnvironmentVariableResolver(env)
1226+ err := cfg.configureProviders(env, resolver, knownProviders)
1227+ require.NoError(t, err)
1229+ // Both providers should be present.
1230+ require.Equal(t, 2, cfg.Providers.Len())
1231+ _, exists := cfg.Providers.Get("openai")
1232+ require.True(t, exists, "openai should be present")
1233+ _, exists = cfg.Providers.Get("anthropic")
1234+ require.True(t, exists, "anthropic should be present")
1237+ t.Run("when enabled, provider missing models is rejected", func(t *testing.T) {
1240+ DisableDefaultProviders: true,
1242+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1244+ APIKey: "test-key",
1245+ BaseURL: "https://my-llm.example.com/v1",
1246+ Models: []catwalk.Model{}, // No models.
1250+ cfg.setDefaults("/tmp", "")
1252+ env := env.NewFromMap(map[string]string{})
1253+ resolver := NewEnvironmentVariableResolver(env)
1254+ err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
1255+ require.NoError(t, err)
1257+ // Provider should be rejected for missing models.
1258+ require.Equal(t, 0, cfg.Providers.Len())
1261+ t.Run("when enabled, provider missing base_url is rejected", func(t *testing.T) {
1264+ DisableDefaultProviders: true,
1266+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1268+ APIKey: "test-key",
1269+ Models: []catwalk.Model{{ID: "model"}},
1274+ cfg.setDefaults("/tmp", "")
1276+ env := env.NewFromMap(map[string]string{})
1277+ resolver := NewEnvironmentVariableResolver(env)
1278+ err := cfg.configureProviders(env, resolver, []catwalk.Provider{})
1279+ require.NoError(t, err)
1281+ // Provider should be rejected for missing base_url.
1282+ require.Equal(t, 0, cfg.Providers.Len())
1286+func TestConfig_setDefaultsDisableDefaultProvidersEnvVar(t *testing.T) {
1287+ t.Run("sets option from environment variable", func(t *testing.T) {
1288+ t.Setenv("CRUSH_DISABLE_DEFAULT_PROVIDERS", "true")
1291+ cfg.setDefaults("/tmp", "")
1293+ require.True(t, cfg.Options.DisableDefaultProviders)
1296+ t.Run("does not override when env var is not set", func(t *testing.T) {
1299+ DisableDefaultProviders: true,
1302+ cfg.setDefaults("/tmp", "")
1304+ require.True(t, cfg.Options.DisableDefaultProviders)
1098func TestConfig_configureSelectedModels(t *testing.T) {
1308func TestConfig_configureSelectedModels(t *testing.T) {
1099 t.Run("should override defaults", func(t *testing.T) {
1309 t.Run("should override defaults", func(t *testing.T) {
1100 knownProviders := []catwalk.Provider{
1310 knownProviders := []catwalk.Provider{