package config import ( "testing" "time" ) // What counts as a LOCAL model alias, and where that is demanded (ADR-034, ADR-035 review). // A private transcript may be sent to a local model and to nothing else; the check is a // guard against a misconfiguration or a crafted alias, so it must be strict about shape, // not just about a prefix. func TestIsLocalModel_AcceptsRealLocalAliasesAndNothingShapedLikeAnEscape(t *testing.T) { tests := []struct { alias string want bool }{ {"koala/phi4-mini", true}, {"iguana/gemma4-26b", true}, {"iguana/gemma4-31b-think", true}, {"koala/qwen36-35b-a3b", true}, {"iguana/kb-whisper", true}, {"iguana/hf.co-model_v1:q4", true}, {"", false}, {"koala/", false}, {"iguana/", false}, {"gemma4-26b", false}, {"berget/mistral-small", false}, {"berget/koala/x", false}, {"xkoala/x", false}, {"Koala/x", false}, {" koala/x", false}, {"koala/x ", false}, {"koala/x y", false}, {"koala/x\n", false}, {"koala/../berget/kb", false}, {"koala//berget/mistral-small", false}, {"koala/x/y", false}, {"koala/x?host=berget", false}, {"koala/x#frag", false}, {"koala/x@evil", false}, {"koalа/x", false}, // Cyrillic а } for _, tt := range tests { t.Run(tt.alias, func(t *testing.T) { if got := IsLocalModel(tt.alias); got != tt.want { t.Errorf("IsLocalModel(%q) = %v, want %v", tt.alias, got, tt.want) } }) } } func TestLoad_UploadChatModelMustBeLocalBecauseTheWholeTranscriptIsSentToIt(t *testing.T) { for _, alias := range []string{"berget/mistral-small", "gemini/gemini-2.5-pro", "gemma4-26b", "koala/../berget/kb"} { t.Run(alias, func(t *testing.T) { clearUploadEnv(t) setEnv(t, map[string]string{"TAPIR_UPLOAD_CHAT_MODEL": alias}) if _, err := Load(); err == nil { t.Errorf("Load accepted TAPIR_UPLOAD_CHAT_MODEL=%q: a private transcript of up to 90,000 characters would be sent to it", alias) } }) } } func TestLoad_ALocalUploadChatModelIsAccepted(t *testing.T) { clearUploadEnv(t) setEnv(t, map[string]string{"TAPIR_UPLOAD_CHAT_MODEL": "iguana/gemma4-31b"}) cfg, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if cfg.UploadChatModel != "iguana/gemma4-31b" { t.Errorf("UploadChatModel = %q", cfg.UploadChatModel) } } func TestLoad_TheEngineTimeoutDefaultsToTwentyMinutesAndMustBePositive(t *testing.T) { clearUploadEnv(t) cfg, err := Load() if err != nil { t.Fatalf("Load: %v", err) } if cfg.TranscribeEngineTimeout != 20*time.Minute { t.Errorf("TranscribeEngineTimeout = %v, want 20m: a 90-minute recording takes about 8 minutes on kb-whisper, and two wedged engines in sequence must leave the job time to reach a fallback", cfg.TranscribeEngineTimeout) } setEnv(t, map[string]string{"TAPIR_TRANSCRIBE_ENGINE_TIMEOUT": "7m"}) cfg, err = Load() if err != nil || cfg.TranscribeEngineTimeout != 7*time.Minute { t.Errorf("override: %v, %v", cfg.TranscribeEngineTimeout, err) } for _, bad := range []string{"0", "-1m", "soon"} { setEnv(t, map[string]string{"TAPIR_TRANSCRIBE_ENGINE_TIMEOUT": bad}) if _, err := Load(); err == nil { t.Errorf("Load accepted TAPIR_TRANSCRIBE_ENGINE_TIMEOUT=%q", bad) } } }