add tests

This commit is contained in:
LeonardoTrapani
2025-08-21 18:09:00 +02:00
parent ddef3435f6
commit 03e5e6e66b
10 changed files with 3603 additions and 0 deletions
+776
View File
@@ -0,0 +1,776 @@
package config
import (
"os"
"path/filepath"
"testing"
"time"
)
// createTestConfig returns a valid configuration for testing
func createTestConfig() *Config {
return &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
Device: "",
ChannelBufferSize: 30,
Timeout: 5 * time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-api-key",
Language: "",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
RestoreClipboard: true,
WtypeTimeout: 5 * time.Second,
ClipboardTimeout: 3 * time.Second,
},
Notifications: NotificationsConfig{
Enabled: true,
Type: "log",
},
}
}
// createTestConfigWithInvalidValues returns a config with invalid values for testing validation
func createTestConfigWithInvalidValues() *Config {
return &Config{
Recording: RecordingConfig{
SampleRate: 0, // Invalid
Channels: 0, // Invalid
Format: "", // Invalid
BufferSize: 0, // Invalid
ChannelBufferSize: 0, // Invalid
Timeout: 0, // Invalid
},
Transcription: TranscriptionConfig{
Provider: "", // Invalid
APIKey: "", // Invalid
Model: "", // Invalid
},
Injection: InjectionConfig{
Mode: "invalid", // Invalid
WtypeTimeout: 0, // Invalid
ClipboardTimeout: 0, // Invalid
},
Notifications: NotificationsConfig{
Type: "invalid", // Invalid
},
}
}
func TestConfig_Validate(t *testing.T) {
tests := []struct {
name string
config *Config
wantErr bool
}{
{
name: "valid config",
config: createTestConfig(),
wantErr: false,
},
{
name: "invalid config",
config: createTestConfigWithInvalidValues(),
wantErr: true,
},
{
name: "invalid recording sample rate",
config: &Config{
Recording: RecordingConfig{
SampleRate: 0,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
},
wantErr: true,
},
{
name: "invalid transcription provider",
config: &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "",
APIKey: "test-key",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
},
wantErr: true,
},
{
name: "invalid injection mode",
config: &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "invalid",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
},
wantErr: true,
},
{
name: "invalid notification type",
config: &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "invalid",
},
},
wantErr: true,
},
{
name: "valid language codes",
config: &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Language: "en",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
},
wantErr: false,
},
{
name: "invalid language code",
config: &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Language: "invalid",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.config.Validate()
if (err != nil) != tt.wantErr {
t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestConfig_Load(t *testing.T) {
// Test that Load creates default config when none exists
t.Run("creates default config when none exists", func(t *testing.T) {
tempDir := t.TempDir()
originalConfigDir := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", tempDir)
defer func() {
if originalConfigDir == "" {
os.Unsetenv("XDG_CONFIG_HOME")
} else {
os.Setenv("XDG_CONFIG_HOME", originalConfigDir)
}
}()
config, err := Load()
if err != nil {
t.Errorf("Load() error = %v", err)
return
}
// Verify the loaded config is valid
if err := config.Validate(); err != nil {
t.Errorf("Loaded config is invalid: %v", err)
}
// Verify config file was created
configPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
if _, err := os.Stat(configPath); os.IsNotExist(err) {
t.Errorf("Load() did not create config file")
}
})
// Test that Load works with existing valid config
t.Run("loads existing valid config", func(t *testing.T) {
tempDir := t.TempDir()
configPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
// Create directory and config file
err := os.MkdirAll(filepath.Dir(configPath), 0755)
if err != nil {
t.Fatalf("Failed to create config directory: %v", err)
}
validConfig := `[recording]
sample_rate = 16000
channels = 1
format = "s16"
buffer_size = 8192
channel_buffer_size = 30
timeout = "5m"
[transcription]
provider = "openai"
api_key = "test-key"
model = "whisper-1"
[injection]
mode = "fallback"
wtype_timeout = "5s"
clipboard_timeout = "3s"
[notifications]
enabled = true
type = "log"`
err = os.WriteFile(configPath, []byte(validConfig), 0644)
if err != nil {
t.Fatalf("Failed to create config file: %v", err)
}
originalConfigDir := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", tempDir)
defer func() {
if originalConfigDir == "" {
os.Unsetenv("XDG_CONFIG_HOME")
} else {
os.Setenv("XDG_CONFIG_HOME", originalConfigDir)
}
}()
config, err := Load()
if err != nil {
t.Errorf("Load() error = %v", err)
return
}
// Verify the loaded config is valid
if err := config.Validate(); err != nil {
t.Errorf("Loaded config is invalid: %v", err)
}
// Verify specific values were loaded
if config.Recording.SampleRate != 16000 {
t.Errorf("Expected SampleRate 16000, got %d", config.Recording.SampleRate)
}
if config.Transcription.Provider != "openai" {
t.Errorf("Expected Provider 'openai', got %s", config.Transcription.Provider)
}
})
}
func TestConfig_SaveDefaultConfig(t *testing.T) {
// Override the config path by setting environment variable
tempDir := t.TempDir()
originalConfigDir := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", tempDir)
defer func() {
if originalConfigDir == "" {
os.Unsetenv("XDG_CONFIG_HOME")
} else {
os.Setenv("XDG_CONFIG_HOME", originalConfigDir)
}
}()
err := SaveDefaultConfig()
if err != nil {
t.Errorf("SaveDefaultConfig() error = %v", err)
return
}
// Verify file was created
configPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
if _, err := os.Stat(configPath); os.IsNotExist(err) {
t.Errorf("SaveDefaultConfig() did not create config file")
return
}
// Verify file content
content, err := os.ReadFile(configPath)
if err != nil {
t.Errorf("Failed to read created config file: %v", err)
return
}
if len(content) == 0 {
t.Errorf("SaveDefaultConfig() created empty config file")
return
}
// Verify it's valid TOML
config, err := Load()
if err != nil {
t.Errorf("SaveDefaultConfig() created invalid config: %v", err)
return
}
// Verify validation passes
if err := config.Validate(); err != nil {
t.Errorf("SaveDefaultConfig() created invalid config: %v", err)
}
}
func TestConfig_ConversionMethods(t *testing.T) {
config := createTestConfig()
t.Run("ToRecordingConfig", func(t *testing.T) {
recordingConfig := config.ToRecordingConfig()
if recordingConfig.SampleRate != config.Recording.SampleRate {
t.Errorf("SampleRate mismatch: got %d, want %d", recordingConfig.SampleRate, config.Recording.SampleRate)
}
if recordingConfig.Channels != config.Recording.Channels {
t.Errorf("Channels mismatch: got %d, want %d", recordingConfig.Channels, config.Recording.Channels)
}
if recordingConfig.Format != config.Recording.Format {
t.Errorf("Format mismatch: got %s, want %s", recordingConfig.Format, config.Recording.Format)
}
})
t.Run("ToTranscriberConfig", func(t *testing.T) {
transcriberConfig := config.ToTranscriberConfig()
if transcriberConfig.Provider != config.Transcription.Provider {
t.Errorf("Provider mismatch: got %s, want %s", transcriberConfig.Provider, config.Transcription.Provider)
}
if transcriberConfig.APIKey != config.Transcription.APIKey {
t.Errorf("APIKey mismatch: got %s, want %s", transcriberConfig.APIKey, config.Transcription.APIKey)
}
if transcriberConfig.Language != config.Transcription.Language {
t.Errorf("Language mismatch: got %s, want %s", transcriberConfig.Language, config.Transcription.Language)
}
if transcriberConfig.Model != config.Transcription.Model {
t.Errorf("Model mismatch: got %s, want %s", transcriberConfig.Model, config.Transcription.Model)
}
})
t.Run("ToInjectionConfig", func(t *testing.T) {
injectionConfig := config.ToInjectionConfig()
if injectionConfig.Mode != config.Injection.Mode {
t.Errorf("Mode mismatch: got %s, want %s", injectionConfig.Mode, config.Injection.Mode)
}
if injectionConfig.RestoreClipboard != config.Injection.RestoreClipboard {
t.Errorf("RestoreClipboard mismatch: got %t, want %t", injectionConfig.RestoreClipboard, config.Injection.RestoreClipboard)
}
if injectionConfig.WtypeTimeout != config.Injection.WtypeTimeout {
t.Errorf("WtypeTimeout mismatch: got %v, want %v", injectionConfig.WtypeTimeout, config.Injection.WtypeTimeout)
}
if injectionConfig.ClipboardTimeout != config.Injection.ClipboardTimeout {
t.Errorf("ClipboardTimeout mismatch: got %v, want %v", injectionConfig.ClipboardTimeout, config.Injection.ClipboardTimeout)
}
})
}
func TestIsValidLanguageCode(t *testing.T) {
validCodes := []string{"en", "es", "fr", "de", "it", "pt", "ru", "ja", "ko", "zh", "ar", "hi"}
invalidCodes := []string{"", "invalid", "xx", "123", "EN", "en-us"}
for _, code := range validCodes {
t.Run("valid_"+code, func(t *testing.T) {
if !isValidLanguageCode(code) {
t.Errorf("isValidLanguageCode(%s) = false, want true", code)
}
})
}
for _, code := range invalidCodes {
t.Run("invalid_"+code, func(t *testing.T) {
if isValidLanguageCode(code) {
t.Errorf("isValidLanguageCode(%s) = true, want false", code)
}
})
}
}
func TestGetConfigPath(t *testing.T) {
// Override user config dir for testing using environment variable
tempDir := t.TempDir()
originalConfigDir := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", tempDir)
defer func() {
if originalConfigDir == "" {
os.Unsetenv("XDG_CONFIG_HOME")
} else {
os.Setenv("XDG_CONFIG_HOME", originalConfigDir)
}
}()
path, err := GetConfigPath()
if err != nil {
t.Errorf("GetConfigPath() error = %v", err)
return
}
expectedPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
if path != expectedPath {
t.Errorf("GetConfigPath() = %s, want %s", path, expectedPath)
}
// Verify directory was created
if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) {
t.Errorf("GetConfigPath() did not create config directory")
}
}
func TestConfig_ToTranscriberConfig_WithEnvVar(t *testing.T) {
config := &Config{
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "", // Empty API key to test env var fallback
Language: "en",
Model: "whisper-1",
},
}
// Set environment variable
originalAPIKey := os.Getenv("OPENAI_API_KEY")
os.Setenv("OPENAI_API_KEY", "env-api-key")
defer func() {
if originalAPIKey == "" {
os.Unsetenv("OPENAI_API_KEY")
} else {
os.Setenv("OPENAI_API_KEY", originalAPIKey)
}
}()
transcriberConfig := config.ToTranscriberConfig()
if transcriberConfig.APIKey != "env-api-key" {
t.Errorf("Expected APIKey from env var 'env-api-key', got %s", transcriberConfig.APIKey)
}
}
func TestConfig_ToTranscriberConfig_WithoutEnvVar(t *testing.T) {
config := &Config{
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "config-api-key", // Config has API key
Language: "en",
Model: "whisper-1",
},
}
// Ensure environment variable is not set
originalAPIKey := os.Getenv("OPENAI_API_KEY")
os.Unsetenv("OPENAI_API_KEY")
defer func() {
if originalAPIKey != "" {
os.Setenv("OPENAI_API_KEY", originalAPIKey)
}
}()
transcriberConfig := config.ToTranscriberConfig()
if transcriberConfig.APIKey != "config-api-key" {
t.Errorf("Expected APIKey from config 'config-api-key', got %s", transcriberConfig.APIKey)
}
}
func TestConfig_Load_InvalidTOML(t *testing.T) {
tempDir := t.TempDir()
configPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
// Create directory and invalid config file
err := os.MkdirAll(filepath.Dir(configPath), 0755)
if err != nil {
t.Fatalf("Failed to create config directory: %v", err)
}
invalidConfig := `[recording]
sample_rate = "invalid_number"`
err = os.WriteFile(configPath, []byte(invalidConfig), 0644)
if err != nil {
t.Fatalf("Failed to create config file: %v", err)
}
originalConfigDir := os.Getenv("XDG_CONFIG_HOME")
os.Setenv("XDG_CONFIG_HOME", tempDir)
defer func() {
if originalConfigDir == "" {
os.Unsetenv("XDG_CONFIG_HOME")
} else {
os.Setenv("XDG_CONFIG_HOME", originalConfigDir)
}
}()
_, err = Load()
if err == nil {
t.Errorf("Load() should have failed with invalid TOML")
}
}
func TestConfig_Validate_OpenAI_WithoutAPIKey(t *testing.T) {
config := &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "", // No API key
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
}
// Ensure environment variable is not set
originalAPIKey := os.Getenv("OPENAI_API_KEY")
os.Unsetenv("OPENAI_API_KEY")
defer func() {
if originalAPIKey != "" {
os.Setenv("OPENAI_API_KEY", originalAPIKey)
}
}()
err := config.Validate()
if err == nil {
t.Errorf("Validate() should have failed without OpenAI API key")
}
}
func TestConfig_Validate_OpenAI_WithEnvVarAPIKey(t *testing.T) {
config := &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "", // No API key in config
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
}
// Set environment variable
originalAPIKey := os.Getenv("OPENAI_API_KEY")
os.Setenv("OPENAI_API_KEY", "env-api-key")
defer func() {
if originalAPIKey == "" {
os.Unsetenv("OPENAI_API_KEY")
} else {
os.Setenv("OPENAI_API_KEY", originalAPIKey)
}
}()
err := config.Validate()
if err != nil {
t.Errorf("Validate() should have passed with OpenAI API key from environment: %v", err)
}
}
func TestConfig_Validate_RecordingTimeout(t *testing.T) {
config := &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: 0, // Invalid timeout
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
}
err := config.Validate()
if err == nil {
t.Errorf("Validate() should have failed with invalid recording timeout")
}
}
func TestConfig_Validate_InjectionTimeouts(t *testing.T) {
config := &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 8192,
ChannelBufferSize: 30,
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: 0, // Invalid timeout
ClipboardTimeout: 0, // Invalid timeout
},
Notifications: NotificationsConfig{
Type: "log",
},
}
err := config.Validate()
if err == nil {
t.Errorf("Validate() should have failed with invalid injection timeouts")
}
}
func TestConfig_Validate_RecordingBufferSizes(t *testing.T) {
config := &Config{
Recording: RecordingConfig{
SampleRate: 16000,
Channels: 1,
Format: "s16",
BufferSize: 0, // Invalid buffer size
ChannelBufferSize: 0, // Invalid buffer size
Timeout: time.Minute,
},
Transcription: TranscriptionConfig{
Provider: "openai",
APIKey: "test-key",
Model: "whisper-1",
},
Injection: InjectionConfig{
Mode: "fallback",
WtypeTimeout: time.Second,
ClipboardTimeout: time.Second,
},
Notifications: NotificationsConfig{
Type: "log",
},
}
err := config.Validate()
if err == nil {
t.Errorf("Validate() should have failed with invalid recording buffer sizes")
}
}