package modelselection import ( "testing" "voicesnap/internal/config" "voicesnap/internal/model" ) type fakeDetector struct { locale string } func (f fakeDetector) Detect() string { return f.locale } func TestResolveAutoUsesEnglishDefaultModel(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeAuto current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.ModelID != model.MoonshineModelID { t.Fatalf("model id = %q, want %q", current.ModelID, model.MoonshineModelID) } if current.Profile.BackendKind != model.BackendMoonshine { t.Fatalf("backend = %q, want %q", current.Profile.BackendKind, model.BackendMoonshine) } } func TestResolveAutoUsesChineseDefaultModel(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeAuto current := Resolve(cfg, fakeDetector{locale: "zh-CN"}) if current.ModelID != model.DefaultModelID { t.Fatalf("model id = %q, want %q", current.ModelID, model.DefaultModelID) } } func TestResolveManualCantoneseUsesSenseVoiceYueDefault(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeManual cfg.LanguageID = model.CantoneseLanguageID cfg.ModelSelectionMode = config.ModelSelectionModeAuto current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.LanguageSettings.EffectiveLanguageID != model.CantoneseLanguageID { t.Fatalf("effective language = %q, want %q", current.LanguageSettings.EffectiveLanguageID, model.CantoneseLanguageID) } if current.ModelID != model.SenseVoiceYueModelID { t.Fatalf("model id = %q, want %q", current.ModelID, model.SenseVoiceYueModelID) } if current.Profile.BackendKind != model.BackendSenseVoice { t.Fatalf("backend = %q, want %q", current.Profile.BackendKind, model.BackendSenseVoice) } } func TestResolveManualCantoneseRejectsChineseUpgradeModels(t *testing.T) { tests := []string{ model.Qwen3ASRModelID, model.XASRZhEn960ModelID, } for _, modelID := range tests { cfg := config.Default() cfg.LanguageMode = config.LanguageModeManual cfg.LanguageID = model.CantoneseLanguageID cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = modelID current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.ModelID != model.SenseVoiceYueModelID { t.Fatalf("%s resolved model id = %q, want %q", modelID, current.ModelID, model.SenseVoiceYueModelID) } if current.FallbackReason != "manual model not available for language" { t.Fatalf("%s fallback reason = %q, want language incompatibility", modelID, current.FallbackReason) } } } func TestResolveAutoUsesEuropeanDefaultModel(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeAuto current := Resolve(cfg, fakeDetector{locale: "fr-FR"}) if current.ModelID != model.ParakeetV3ModelID { t.Fatalf("model id = %q, want %q", current.ModelID, model.ParakeetV3ModelID) } if current.Profile.BackendKind != model.BackendNemoTransducer { t.Fatalf("backend = %q, want %q", current.Profile.BackendKind, model.BackendNemoTransducer) } } func TestResolveAutoUsesJapaneseAndKoreanDefaults(t *testing.T) { tests := []struct { locale string modelID string }{ {"ja-JP", model.JapaneseModelID}, {"ko-KR", model.KoreanModelID}, } for _, tt := range tests { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeAuto current := Resolve(cfg, fakeDetector{locale: tt.locale}) if current.ModelID != tt.modelID { t.Fatalf("%s model id = %q, want %q", tt.locale, current.ModelID, tt.modelID) } if current.Profile.BackendKind != model.BackendTransducer { t.Fatalf("%s backend = %q, want %q", tt.locale, current.Profile.BackendKind, model.BackendTransducer) } } } func TestResolveOldConfigMissingSelectionModeUsesAuto(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = "" cfg.SelectedModelID = model.DefaultModelID current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.SelectionMode != config.ModelSelectionModeAuto { t.Fatalf("selection mode = %q, want auto", current.SelectionMode) } if current.ModelID != model.MoonshineModelID { t.Fatalf("old selected model locked current model to %q, want %q", current.ModelID, model.MoonshineModelID) } } func TestResolveManualUsesSelectedModel(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.ParakeetModelID current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.ModelID != model.ParakeetModelID { t.Fatalf("model id = %q, want manual selected %q", current.ModelID, model.ParakeetModelID) } } func TestResolveManualUsesQwen3ASRForChinese(t *testing.T) { if !model.IsModelSupportedInCurrentBuild(model.Qwen3ASRModelID) { t.Skip("qwen3-asr is disabled in this build") } cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.Qwen3ASRModelID current := Resolve(cfg, fakeDetector{locale: "zh-CN"}) if current.ModelID != model.Qwen3ASRModelID { t.Fatalf("model id = %q, want manual selected %q", current.ModelID, model.Qwen3ASRModelID) } } func TestResolveManualQwen3ASRFallsBackWhenUnsupported(t *testing.T) { if model.IsModelSupportedInCurrentBuild(model.Qwen3ASRModelID) { t.Skip("qwen3-asr is supported in this build") } cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.Qwen3ASRModelID current := Resolve(cfg, fakeDetector{locale: "zh-CN"}) if current.ModelID != model.DefaultModelID { t.Fatalf("model id = %q, want fallback %q", current.ModelID, model.DefaultModelID) } if current.FallbackReason != "manual model not supported in current build" { t.Fatalf("fallback reason = %q, want unsupported build reason", current.FallbackReason) } } func TestResolveManualUsesXASRForChinese(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.XASRZhEn960ModelID current := Resolve(cfg, fakeDetector{locale: "zh-CN"}) if current.ModelID != model.XASRZhEn960ModelID { t.Fatalf("model id = %q, want manual selected %q", current.ModelID, model.XASRZhEn960ModelID) } } func TestResolveManualUsesXASRForEnglish(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.XASRZhEn960ModelID current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.ModelID != model.XASRZhEn960ModelID { t.Fatalf("model id = %q, want manual selected %q", current.ModelID, model.XASRZhEn960ModelID) } } func TestResolveManualModelFallsBackWhenLanguageIncompatible(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.ParakeetModelID current := Resolve(cfg, fakeDetector{locale: "zh-CN"}) if current.ModelID != model.DefaultModelID { t.Fatalf("model id = %q, want Chinese default %q", current.ModelID, model.DefaultModelID) } if current.FallbackReason == "" { t.Fatal("expected fallback reason") } } func TestResolveManualQwen3ASRFallsBackForEnglish(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.Qwen3ASRModelID current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.ModelID != model.MoonshineModelID { t.Fatalf("model id = %q, want English default %q", current.ModelID, model.MoonshineModelID) } if current.FallbackReason == "" { t.Fatal("expected fallback reason") } } func TestResolveManualParakeetV3FallsBackForJapanese(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = model.ParakeetV3ModelID current := Resolve(cfg, fakeDetector{locale: "ja-JP"}) if current.ModelID != model.JapaneseModelID { t.Fatalf("model id = %q, want Japanese default %q", current.ModelID, model.JapaneseModelID) } if current.FallbackReason == "" { t.Fatal("expected fallback reason") } } func TestResolveInvalidManualFallsBackToLanguageDefault(t *testing.T) { cfg := config.Default() cfg.LanguageMode = config.LanguageModeAuto cfg.ModelSelectionMode = config.ModelSelectionModeManual cfg.SelectedModelID = "bad-model" current := Resolve(cfg, fakeDetector{locale: "en-US"}) if current.ModelID != model.MoonshineModelID { t.Fatalf("model id = %q, want %q", current.ModelID, model.MoonshineModelID) } if current.FallbackReason == "" { t.Fatal("expected fallback reason") } }