package config import ( "encoding/json" "os" "path/filepath" "runtime" "testing" "voicesnap/internal/model" "voicesnap/internal/paths" ) func TestDefaultLanguageConfig(t *testing.T) { cfg := Default() if cfg.LanguageMode != LanguageModeAuto { t.Fatalf("language mode = %q, want %q", cfg.LanguageMode, LanguageModeAuto) } if cfg.LanguageID != model.DefaultLanguageID { t.Fatalf("language id = %q, want %q", cfg.LanguageID, model.DefaultLanguageID) } if cfg.SelectedModelID != model.DefaultModelID { t.Fatalf("selected model = %q, want %q", cfg.SelectedModelID, model.DefaultModelID) } if cfg.ModelSelectionMode != ModelSelectionModeAuto { t.Fatalf("model selection mode = %q, want %q", cfg.ModelSelectionMode, ModelSelectionModeAuto) } } func TestDefaultHotkeyForNewConfig(t *testing.T) { tests := []struct { goos string want int }{ {goos: "darwin", want: RightModifierDefaultVK}, {goos: "windows", want: RightModifierDefaultVK}, {goos: "linux", want: LegacyDefaultHotkeyVK}, } for _, tt := range tests { if got := defaultHotkeyVKForNewConfig(tt.goos); got != tt.want { t.Fatalf("defaultHotkeyVKForNewConfig(%q) = 0x%X, want 0x%X", tt.goos, got, tt.want) } } } func TestLoadCreatesFirstConfigWithNewPlatformDefault(t *testing.T) { withIsolatedConfigHome(t) cfg, err := Load() if err != nil { t.Fatalf("Load() error = %v", err) } if got, want := cfg.HotkeyVK, defaultHotkeyVKForNewConfig(runtime.GOOS); got != want { t.Fatalf("HotkeyVK = 0x%X, want 0x%X", got, want) } if cfg.HotkeyMode != HotkeyModeHold { t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeHold) } raw := readConfigJSON(t) if got, ok := raw["HotkeyVK"].(float64); !ok || int(got) != cfg.HotkeyVK { t.Fatalf("saved HotkeyVK = %#v, want %d", raw["HotkeyVK"], cfg.HotkeyVK) } if got := raw["HotkeyMode"]; got != HotkeyModeHold { t.Fatalf("saved HotkeyMode = %#v, want %q", got, HotkeyModeHold) } } func TestLoadExistingConfigPreservesHotkeyAndMode(t *testing.T) { withIsolatedConfigHome(t) writeConfigJSON(t, map[string]interface{}{ "HotkeyVK": 0x41, "HotkeyMode": HotkeyModeTap, }) cfg, err := Load() if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.HotkeyVK != 0x41 { t.Fatalf("HotkeyVK = 0x%X, want 0x41", cfg.HotkeyVK) } if cfg.HotkeyMode != HotkeyModeTap { t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeTap) } } func TestLoadExistingConfigMissingHotkeyUsesLegacyFallback(t *testing.T) { withIsolatedConfigHome(t) writeConfigJSON(t, map[string]interface{}{ "HotkeyMode": HotkeyModeTap, }) cfg, err := Load() if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.HotkeyVK != LegacyDefaultHotkeyVK { t.Fatalf("HotkeyVK = 0x%X, want legacy 0x%X", cfg.HotkeyVK, LegacyDefaultHotkeyVK) } if cfg.HotkeyVK == RightModifierDefaultVK { t.Fatalf("existing config missing HotkeyVK must not inherit new default 0x%X", RightModifierDefaultVK) } if cfg.HotkeyMode != HotkeyModeTap { t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeTap) } } func TestLoadExistingConfigMissingModeKeepsLegacyHoldFallback(t *testing.T) { withIsolatedConfigHome(t) writeConfigJSON(t, map[string]interface{}{ "HotkeyVK": 0x41, }) cfg, err := Load() if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.HotkeyVK != 0x41 { t.Fatalf("HotkeyVK = 0x%X, want 0x41", cfg.HotkeyVK) } if cfg.HotkeyMode != HotkeyModeHold { t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeHold) } } func TestExistingConfigFallbackUsesLegacyHotkey(t *testing.T) { cfg := ExistingConfigFallback() if cfg.HotkeyVK != LegacyDefaultHotkeyVK { t.Fatalf("HotkeyVK = 0x%X, want legacy 0x%X", cfg.HotkeyVK, LegacyDefaultHotkeyVK) } if cfg.HotkeyVK == RightModifierDefaultVK { t.Fatalf("existing config fallback must not inherit new default 0x%X", RightModifierDefaultVK) } if cfg.HotkeyMode != HotkeyModeHold { t.Fatalf("HotkeyMode = %q, want %q", cfg.HotkeyMode, HotkeyModeHold) } } func TestNormalizeLanguageMode(t *testing.T) { if got := NormalizeLanguageMode(LanguageModeManual); got != LanguageModeManual { t.Fatalf("manual normalized to %q", got) } if got := NormalizeLanguageMode("bad"); got != LanguageModeAuto { t.Fatalf("bad mode normalized to %q, want %q", got, LanguageModeAuto) } } func TestNormalizeModelSelectionMode(t *testing.T) { if got := NormalizeModelSelectionMode(ModelSelectionModeManual); got != ModelSelectionModeManual { t.Fatalf("manual normalized to %q", got) } if got := NormalizeModelSelectionMode("bad"); got != ModelSelectionModeAuto { t.Fatalf("bad mode normalized to %q, want %q", got, ModelSelectionModeAuto) } } func withIsolatedConfigHome(t *testing.T) { t.Helper() home := t.TempDir() t.Setenv("HOME", home) t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config")) } func writeConfigJSON(t *testing.T, value map[string]interface{}) { t.Helper() if err := paths.Ensure(); err != nil { t.Fatalf("paths.Ensure() error = %v", err) } data, err := json.Marshal(value) if err != nil { t.Fatalf("json.Marshal() error = %v", err) } if err := os.WriteFile(paths.File("config.json"), data, 0644); err != nil { t.Fatalf("write config error = %v", err) } } func readConfigJSON(t *testing.T) map[string]interface{} { t.Helper() data, err := os.ReadFile(paths.File("config.json")) if err != nil { t.Fatalf("read config error = %v", err) } var raw map[string]interface{} if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("json.Unmarshal() error = %v", err) } return raw }