package config import ( "strings" "testing" ) // Why the assistant is off. // // "Off" was the same answer for four different mistakes, and the only symptom // was a disabled composer. Nobody could tell "we have not switched it on" from // "somebody misspelled a variable" — which is how it stayed off for days with // both of us guessing. func TestAFullyConfiguredAssistantIsOn(t *testing.T) { cfg := AssistantConfig{ Provider: "openai", BaseURL: "https://api.groq.com/openai/v1", APIKey: "k", Balanced: "openai/gpt-oss-120b", } if !cfg.Enabled() { t.Fatalf("a complete config was refused: %s", cfg.Why()) } if cfg.Why() != "" { t.Fatalf("an enabled assistant gave a reason: %q", cfg.Why()) } } func TestEachMissingPieceNamesItself(t *testing.T) { for name, tc := range map[string]struct { cfg AssistantConfig says string }{ // Nothing set at all names the MODEL, not the provider. The provider is // derived from the model now, so an empty one is a consequence rather // than a cause — and sending an operator to set ASSISTANT_PROVIDER, a // variable they no longer need, while the one they actually missed goes // unmentioned, is the same "off for four reasons" problem in new words. "nothing set at all": {AssistantConfig{}, "ASSISTANT_MODEL"}, "no provider": { AssistantConfig{Balanced: "m", APIKey: "k"}, "ASSISTANT_PROVIDER"}, "unknown provider": { AssistantConfig{Provider: "anthropik", Balanced: "m", APIKey: "k"}, "not one this server speaks"}, "no model": {AssistantConfig{Provider: "openai", APIKey: "k"}, "ASSISTANT_MODEL"}, "no api key": {AssistantConfig{Provider: "openai", Balanced: "m", BaseURL: "https://api.groq.com/openai/v1"}, "ASSISTANT_API_KEY"}, } { why := tc.cfg.Why() if why == "" { t.Fatalf("%s: reported as working", name) } if !strings.Contains(why, tc.says) { t.Fatalf("%s: does not name the problem: %q", name, why) } } } func TestALocalModelNeedsNoKey(t *testing.T) { // Ollama and LM Studio need no credential, and demanding one would refuse // the setup a developer is most likely to have on their own machine. for _, base := range []string{ "http://localhost:11434/v1", "http://127.0.0.1:1234/v1", "http://host.docker.internal:11434/v1", } { cfg := AssistantConfig{Provider: "openai", BaseURL: base, Balanced: "llama3"} if !cfg.Enabled() { t.Fatalf("%s was refused without a key: %s", base, cfg.Why()) } } } func TestAHostedModelWithoutAKeyIsRefusedBeforeItFailsAtRuntime(t *testing.T) { // Otherwise the first question a shopkeeper asks comes back as a 401 from // the provider, which reads as the assistant being broken rather than as a // variable nobody set. cfg := AssistantConfig{Provider: "openai", BaseURL: "https://api.groq.com/openai/v1", Balanced: "m"} if cfg.Enabled() { t.Fatal("a hosted provider with no key reported as ready") } } func TestTheTierFallbackDoesNotHideAMissingModel(t *testing.T) { // `fast` and `deep` fall back to balanced, so a config with only those two // set has no model at all for the default tier. cfg := AssistantConfig{Provider: "openai", APIKey: "k", Fast: "small", Deep: "big"} if cfg.Enabled() { t.Fatal("an assistant with no balanced model reported as ready") } if cfg.ModelFor("fast") != "" && cfg.ModelFor("balanced") != "" { t.Fatal("balanced resolved to something despite being unset") } } // Where a secret is allowed to live. // // `.env`, `.env.local` and `.env.production` are all tracked by git, so a key // written to any of them is a key published. There was nowhere else, and the // standing instruction was to export it in the shell on every run — which is // the kind of instruction people route around by editing a tracked file. func TestASecretsFileIsReadBeforeAnyTrackedEnvFile(t *testing.T) { order := envFileOrder("local") if len(order) == 0 || order[0] != ".env.secrets" { t.Fatalf(".env.secrets is not read first, so a tracked file wins: %v", order) } // godotenv does not overwrite, so being first IS what makes it authoritative. // Being merely present would let .env.local decide the key instead. for _, tracked := range []string{".env.local", ".env"} { for i, name := range order { if name == tracked && i == 0 { t.Fatalf("%s is read first; a secret there would be committed", tracked) } } } } func TestTheEnvironmentsOwnFileBeatsTheSharedOne(t *testing.T) { // `.env.production` must be consulted before the shared `.env`, or a // production deployment silently takes the local defaults. order := envFileOrder("production") var production, shared int = -1, -1 for i, name := range order { switch name { case ".env.production": production = i case ".env": shared = i } } if production < 0 || shared < 0 || production > shared { t.Fatalf("the environment's own file does not take precedence: %v", order) } } // One variable, not four. // // The assistant sat switched off for days because `ASSISTANT_PROVIDER` had not // been typed into a hosting platform's environment tab — a variable whose only // correct value is "openai", because every endpoint this server speaks is // OpenAI-compatible. The base URL and the model had one right answer too. // // So three of the four are constants now. The key is the only one that varies // between deployments and the only one that cannot live in the repository. func TestTheKeyAloneSwitchesTheAssistantOn(t *testing.T) { for _, name := range []string{ "ASSISTANT_PROVIDER", "ASSISTANT_BASE_URL", "ASSISTANT_MODEL", "ASSISTANT_MODEL_BALANCED", "ASSISTANT_MODEL_FAST", "ASSISTANT_MODEL_DEEP", } { t.Setenv(name, "") } t.Setenv("ASSISTANT_API_KEY", "gsk_not-a-real-key") cfg := AssistantConfig{ Provider: assistantProvider(), BaseURL: env("ASSISTANT_BASE_URL", defaultAssistantBaseURL), APIKey: env("ASSISTANT_API_KEY", ""), Balanced: env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", defaultAssistantModel)), } if !cfg.Enabled() { t.Fatalf("the key alone did not switch it on: %s", cfg.Why()) } if cfg.Provider != "openai" { t.Fatalf("provider defaulted to %q", cfg.Provider) } if cfg.ModelFor("fast") != defaultAssistantModel { t.Fatalf("the fast tier fell through to %q", cfg.ModelFor("fast")) } } func TestNoKeyIsStillOffAndSaysWhich(t *testing.T) { // The defaults must not make an unconfigured deployment look ready. Without // a key every question would reach Groq and come back 401, which reads as // the assistant being broken rather than as not being set up. cfg := AssistantConfig{ Provider: defaultAssistantProvider, BaseURL: defaultAssistantBaseURL, Balanced: defaultAssistantModel, } if cfg.Enabled() { t.Fatal("reported ready with no key") } if !strings.Contains(cfg.Why(), "ASSISTANT_API_KEY") { t.Fatalf("did not name the one variable left to set: %q", cfg.Why()) } } func TestEachDefaultIsStillOverridable(t *testing.T) { // Running against Ollama on a laptop must not need a code change. t.Setenv("ASSISTANT_PROVIDER", "ollama") t.Setenv("ASSISTANT_BASE_URL", "http://localhost:11434/v1") t.Setenv("ASSISTANT_MODEL", "llama3") cfg := AssistantConfig{ Provider: assistantProvider(), BaseURL: env("ASSISTANT_BASE_URL", defaultAssistantBaseURL), APIKey: env("ASSISTANT_API_KEY", ""), Balanced: env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", defaultAssistantModel)), } if cfg.Provider != "ollama" || cfg.Balanced != "llama3" { t.Fatalf("an override was ignored: %+v", cfg) } // Local endpoints need no key, so this must be on without one. if !cfg.Enabled() { t.Fatalf("a local model was refused: %s", cfg.Why()) } }