fetch_ml/tests/unit/config/validation_test.go
Jeremie Fraeys ea15af1833 Fix multi-user authentication and clean up debug code
- Fix YAML tags in auth config struct (json -> yaml)
- Update CLI configs to use pre-hashed API keys
- Remove double hashing in WebSocket client
- Fix port mapping (9102 -> 9103) in CLI commands
- Update permission keys to use jobs:read, jobs:create, etc.
- Clean up all debug logging from CLI and server
- All user roles now authenticate correctly:
  * Admin: Can queue jobs and see all jobs
  * Researcher: Can queue jobs and see own jobs
  * Analyst: Can see status (read-only access)

Multi-user authentication is now fully functional.
2025-12-06 12:35:32 -05:00

213 lines
5.6 KiB
Go

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 {
shouldFail bool
errorMsg string
}
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")
}
}