package config import ( "fmt" "os" "path/filepath" "testing" "github.com/jfraeys/fetch_ml/internal/config" ) // MockValidator implements the Validator interface for testing type MockValidator struct { errorMsg string shouldFail bool } func (m *MockValidator) Validate() error { if m.shouldFail { return fmt.Errorf("validation error: %s", m.errorMsg) } return nil } func TestValidateConfig(t *testing.T) { t.Parallel() // Enable parallel execution // Test with valid validator validValidator := &MockValidator{shouldFail: false} err := config.ValidateConfig(validValidator) if err != nil { t.Errorf("Expected no error for valid validator, got %v", err) } // Test with invalid validator invalidValidator := &MockValidator{shouldFail: true, errorMsg: "validation failed"} err = config.ValidateConfig(invalidValidator) if err == nil { t.Error("Expected error for invalid validator") } if err.Error() != "validation error: validation failed" { t.Errorf("Expected error message 'validation error: validation failed', got %s", err.Error()) } } func TestValidatePort(t *testing.T) { t.Parallel() // Enable parallel execution // Test valid ports validPorts := []int{1, 22, 80, 443, 6379, 65535} for _, port := range validPorts { err := config.ValidatePort(port) if err != nil { t.Errorf("Expected no error for valid port %d, got %v", port, err) } } // Test invalid ports invalidPorts := []int{0, -1, 65536, 100000} for _, port := range invalidPorts { err := config.ValidatePort(port) if err == nil { t.Errorf("Expected error for invalid port %d", port) } } } func TestValidateDirectory(t *testing.T) { t.Parallel() // Enable parallel execution // Test empty path err := config.ValidateDirectory("") if err == nil { t.Error("Expected error for empty path") } // Test non-existent directory err = config.ValidateDirectory("/nonexistent/directory") if err == nil { t.Error("Expected error for non-existent directory") } // Test existing directory tempDir := t.TempDir() err = config.ValidateDirectory(tempDir) if err != nil { t.Errorf("Expected no error for existing directory %s, got %v", tempDir, err) } // Test file instead of directory tempFile := filepath.Join(tempDir, "test_file") err = os.WriteFile(tempFile, []byte("test"), 0600) if err != nil { t.Fatalf("Failed to create test file: %v", err) } err = config.ValidateDirectory(tempFile) if err == nil { t.Error("Expected error for file path") } // Test directory with environment variable expansion // Reuse the tempDir path so validation succeeds after expansion _ = os.Setenv("TEST_DIR", tempDir) defer func() { _ = os.Unsetenv("TEST_DIR") }() err = config.ValidateDirectory("$TEST_DIR") if err != nil { t.Errorf("Expected no error for expanded directory path, got %v", err) } // Test directory with tilde expansion (if home directory is available) home, err := os.UserHomeDir() if err == nil { // Create a test directory in home testHomeDir := filepath.Join(home, "test_fetch_ml") err = os.MkdirAll(testHomeDir, 0750) if err == nil { defer func() { _ = os.RemoveAll(tempDir) }() err = config.ValidateDirectory("~/test_fetch_ml") if err != nil { t.Errorf("Expected no error for tilde expanded path, got %v", err) } } } } func TestValidateRedisAddr(t *testing.T) { t.Parallel() // Enable parallel execution // Test valid Redis addresses validAddrs := []string{ "localhost:6379", "127.0.0.1:6379", "redis.example.com:6379", "10.0.0.1:6380", "[::1]:6379", // IPv6 } for _, addr := range validAddrs { err := config.ValidateRedisAddr(addr) if err != nil { t.Errorf("Expected no error for valid Redis address %s, got %v", addr, err) } } // Test invalid Redis addresses invalidAddrs := []string{ "", // empty "localhost", // missing port ":6379", // missing host "localhost:", // missing port number "localhost:abc", // non-numeric port "localhost:-1", // negative port "localhost:0", // port too low "localhost:65536", // port too high "localhost:999999", // port way too high "multiple:colons:6379", // too many colons } for _, addr := range invalidAddrs { err := config.ValidateRedisAddr(addr) if err == nil { t.Errorf("Expected error for invalid Redis address %s", addr) } } } func TestValidateRedisAddrEdgeCases(t *testing.T) { t.Parallel() // Enable parallel execution // Test edge case ports edgeCases := []struct { addr string shouldErr bool }{ {"localhost:1", false}, // minimum valid port {"localhost:65535", false}, // maximum valid port {"localhost:0", true}, // below minimum {"localhost:65536", true}, // above maximum } for _, tc := range edgeCases { err := config.ValidateRedisAddr(tc.addr) if tc.shouldErr && err == nil { t.Errorf("Expected error for Redis address %s", tc.addr) } if !tc.shouldErr && err != nil { t.Errorf("Expected no error for Redis address %s, got %v", tc.addr, err) } } } func TestValidatorInterface(t *testing.T) { t.Parallel() // Enable parallel execution // Test that our mock properly implements the interface var _ config.Validator = &MockValidator{} // Test that the interface works as expected validator := &MockValidator{shouldFail: false} err := validator.Validate() if err != nil { t.Errorf("MockValidator should not fail when shouldFail is false") } validator = &MockValidator{shouldFail: true, errorMsg: "test error"} err = validator.Validate() if err == nil { t.Error("MockValidator should fail when shouldFail is true") } }