173 lines
6.2 KiB
Go
173 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// setBaseEnv sets the minimal valid environment (all cascade flags off) so each test
|
|
// can toggle one combination and assert the fail-fast validation (F-FUNC-9).
|
|
func setBaseEnv(t *testing.T) {
|
|
t.Helper()
|
|
t.Setenv("HOMESERVER_URL", "http://hs")
|
|
t.Setenv("BOT_MXID", "@ai:vojo.chat")
|
|
t.Setenv("AS_TOKEN", "as")
|
|
t.Setenv("HS_TOKEN", "hs")
|
|
t.Setenv("XAI_API_KEY", "xai")
|
|
t.Setenv("AI_BOT_DATABASE_URL", "postgres://x")
|
|
t.Setenv("ALLOWED_SERVERS", "vojo.chat")
|
|
// Force a clean baseline so the host environment can't leak in.
|
|
for _, k := range []string{
|
|
"GEMINI_API_KEY", "GEMINI_API_KEY_FILE", "ROUTER_ENABLED", "ROUTER_CLASSIFIER_ENABLED",
|
|
"TRIVIAL_OFFLOAD_ENABLED", "WEB_ENABLED", "REASONING_ENABLED", "WEB_PROVIDER", "REASONING_MODEL",
|
|
"WEB_PARANOID", "WEB_GROUNDING_DAILY_CAP", "GEMINI_GROUNDING_PER_PROMPT_USD",
|
|
"PROJECT_KB_ENABLED", "PROJECT_KB_PATH",
|
|
} {
|
|
t.Setenv(k, "")
|
|
}
|
|
}
|
|
|
|
func TestConfigBaseValid(t *testing.T) {
|
|
setBaseEnv(t)
|
|
if _, err := LoadConfig(); err != nil {
|
|
t.Fatalf("base config should be valid: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConfigAllCascadeFlagsDefaultOff(t *testing.T) {
|
|
setBaseEnv(t)
|
|
cfg, err := LoadConfig()
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
if cfg.RouterEnabled || cfg.RouterClassifierEnabled || cfg.TrivialOffloadEnabled ||
|
|
cfg.WebEnabled || cfg.ReasoningEnabled || cfg.TelemetryEnabled || cfg.GrokPromptCache {
|
|
t.Fatal("every cascade/telemetry flag must default off (cascade-off == today)")
|
|
}
|
|
if cfg.WebProvider != webProviderGrokWebSearch {
|
|
t.Fatalf("default WEB_PROVIDER = %q, want grok_web_search", cfg.WebProvider)
|
|
}
|
|
}
|
|
|
|
func TestConfigTrivialNeedsGeminiKey(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("TRIVIAL_OFFLOAD_ENABLED", "true")
|
|
if _, err := LoadConfig(); err == nil || !strings.Contains(err.Error(), "GEMINI_API_KEY") {
|
|
t.Fatalf("want GEMINI_API_KEY error, got %v", err)
|
|
}
|
|
t.Setenv("GEMINI_API_KEY", "gk")
|
|
if _, err := LoadConfig(); err != nil {
|
|
t.Fatalf("with key it should be valid: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConfigClassifierNeedsRouter(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("GEMINI_API_KEY", "gk")
|
|
t.Setenv("ROUTER_CLASSIFIER_ENABLED", "true") // without ROUTER_ENABLED
|
|
if _, err := LoadConfig(); err == nil || !strings.Contains(err.Error(), "ROUTER_ENABLED") {
|
|
t.Fatalf("want ROUTER_ENABLED error, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestConfigProjectKBDefaultsPath: PROJECT_KB_PATH defaults to the bundled KB, so enabling
|
|
// the route needs only PROJECT_KB_ENABLED=true (the classifier already on). LoadConfig does
|
|
// not read the file — main.go does the fail-closed read/empty/size check at startup.
|
|
func TestConfigProjectKBDefaultsPath(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("GEMINI_API_KEY", "gk")
|
|
t.Setenv("ROUTER_ENABLED", "true")
|
|
t.Setenv("ROUTER_CLASSIFIER_ENABLED", "true")
|
|
t.Setenv("PROJECT_KB_ENABLED", "true") // no explicit PROJECT_KB_PATH → bundled default
|
|
cfg, err := LoadConfig()
|
|
if err != nil {
|
|
t.Fatalf("enabling with the default KB path should be valid: %v", err)
|
|
}
|
|
if cfg.ProjectKBPath != "prompts/vojo_kb.txt" {
|
|
t.Fatalf("PROJECT_KB_PATH default = %q, want prompts/vojo_kb.txt", cfg.ProjectKBPath)
|
|
}
|
|
}
|
|
|
|
// TestConfigProjectKBNeedsClassifier: PROJECT_KB_ENABLED requires ROUTER_CLASSIFIER_ENABLED
|
|
// (the about_project gate is a classifier signal; without it the route could never fire).
|
|
func TestConfigProjectKBNeedsClassifier(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("PROJECT_KB_ENABLED", "true")
|
|
t.Setenv("PROJECT_KB_PATH", "/tmp/vojo_kb.txt") // classifier deliberately off
|
|
if _, err := LoadConfig(); err == nil || !strings.Contains(err.Error(), "ROUTER_CLASSIFIER_ENABLED") {
|
|
t.Fatalf("PROJECT_KB_ENABLED without the classifier should fail; got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestConfigBadWebProvider(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("WEB_ENABLED", "true")
|
|
t.Setenv("WEB_PROVIDER", "bing")
|
|
if _, err := LoadConfig(); err == nil || !strings.Contains(err.Error(), "WEB_PROVIDER") {
|
|
t.Fatalf("want WEB_PROVIDER error, got %v", err)
|
|
}
|
|
}
|
|
|
|
// The default web provider (grok_web_search) uses the existing xAI key, so WEB_ENABLED
|
|
// alone must NOT demand a Gemini key.
|
|
func TestConfigWebGrokNeedsNoGeminiKey(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("WEB_ENABLED", "true")
|
|
if _, err := LoadConfig(); err != nil {
|
|
t.Fatalf("web+grok_web_search should not need a Gemini key: %v", err)
|
|
}
|
|
}
|
|
|
|
// gemini_grounding DOES need a Gemini key.
|
|
func TestConfigWebGeminiGroundingNeedsKey(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("WEB_ENABLED", "true")
|
|
t.Setenv("WEB_PROVIDER", webProviderGeminiGrounding)
|
|
if _, err := LoadConfig(); err == nil || !strings.Contains(err.Error(), "GEMINI_API_KEY") {
|
|
t.Fatalf("want GEMINI_API_KEY error, got %v", err)
|
|
}
|
|
}
|
|
|
|
// §7 SG3: paranoid web on the uncapped grok_web_search must refuse to boot; with
|
|
// gemini_grounding (+ key) it is valid.
|
|
func TestConfigParanoidRequiresGeminiGrounding(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("WEB_ENABLED", "true")
|
|
t.Setenv("WEB_PARANOID", "true") // default provider is grok_web_search
|
|
if _, err := LoadConfig(); err == nil || !strings.Contains(err.Error(), "WEB_PARANOID") {
|
|
t.Fatalf("want WEB_PARANOID error on grok_web_search, got %v", err)
|
|
}
|
|
t.Setenv("WEB_PROVIDER", webProviderGeminiGrounding)
|
|
t.Setenv("GEMINI_API_KEY", "gk")
|
|
if _, err := LoadConfig(); err != nil {
|
|
t.Fatalf("paranoid + gemini_grounding should be valid: %v", err)
|
|
}
|
|
}
|
|
|
|
// §7 SG5: a non-positive grounding cap silently disables grounding — refuse it for
|
|
// gemini_grounding.
|
|
func TestConfigGeminiGroundingCapMustBePositive(t *testing.T) {
|
|
setBaseEnv(t)
|
|
t.Setenv("WEB_ENABLED", "true")
|
|
t.Setenv("WEB_PROVIDER", webProviderGeminiGrounding)
|
|
t.Setenv("GEMINI_API_KEY", "gk")
|
|
t.Setenv("WEB_GROUNDING_DAILY_CAP", "0")
|
|
if _, err := LoadConfig(); err == nil || !strings.Contains(err.Error(), "WEB_GROUNDING_DAILY_CAP") {
|
|
t.Fatalf("want WEB_GROUNDING_DAILY_CAP error, got %v", err)
|
|
}
|
|
}
|
|
|
|
// The default per-prompt grounding fee is the paid-tier $0.035 (the operator must opt to 0).
|
|
func TestConfigGroundingFeeDefault(t *testing.T) {
|
|
setBaseEnv(t)
|
|
cfg, err := LoadConfig()
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
if cfg.GeminiGroundingPerPrompt != 0.035 {
|
|
t.Fatalf("GEMINI_GROUNDING_PER_PROMPT_USD default = %v, want 0.035", cfg.GeminiGroundingPerPrompt)
|
|
}
|
|
if cfg.WebParanoid {
|
|
t.Fatal("WEB_PARANOID must default off")
|
|
}
|
|
}
|