- Add end-to-end tests for complete workflow validation - Include integration tests for API and database interactions - Add unit tests for all major components and utilities - Include performance tests for payload handling - Add CLI API integration tests - Include Podman container integration tests - Add WebSocket and queue execution tests - Include shell script tests for setup validation Provides comprehensive test coverage ensuring platform reliability and functionality across all components and interactions.
333 lines
7.9 KiB
Go
333 lines
7.9 KiB
Go
package auth
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/auth"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// ConfigWithAuth holds configuration with authentication
|
|
type ConfigWithAuth struct {
|
|
Auth auth.AuthConfig `yaml:"auth"`
|
|
}
|
|
|
|
func TestUserManagerGenerateKey(t *testing.T) {
|
|
// Create temporary config file
|
|
tempDir := t.TempDir()
|
|
configFile := filepath.Join(tempDir, "test_config.yaml")
|
|
|
|
// Initial config with auth enabled
|
|
config := ConfigWithAuth{
|
|
Auth: auth.AuthConfig{
|
|
Enabled: true,
|
|
APIKeys: map[auth.Username]auth.APIKeyEntry{
|
|
"existing_user": {
|
|
Hash: auth.APIKeyHash(auth.HashAPIKey("existing-key")),
|
|
Admin: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal config: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configFile, data, 0644); err != nil {
|
|
t.Fatalf("Failed to write config: %v", err)
|
|
}
|
|
|
|
// Test generate-key command
|
|
configData, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read config: %v", err)
|
|
}
|
|
|
|
var cfg ConfigWithAuth
|
|
if err := yaml.Unmarshal(configData, &cfg); err != nil {
|
|
t.Fatalf("Failed to parse config: %v", err)
|
|
}
|
|
|
|
// Generate API key
|
|
apiKey := auth.GenerateAPIKey()
|
|
|
|
// Add to config
|
|
if cfg.Auth.APIKeys == nil {
|
|
cfg.Auth.APIKeys = make(map[auth.Username]auth.APIKeyEntry)
|
|
}
|
|
cfg.Auth.APIKeys[auth.Username("test_user")] = auth.APIKeyEntry{
|
|
Hash: auth.APIKeyHash(auth.HashAPIKey(apiKey)),
|
|
Admin: false,
|
|
}
|
|
|
|
// Save config
|
|
updatedData, err := yaml.Marshal(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal updated config: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configFile, updatedData, 0644); err != nil {
|
|
t.Fatalf("Failed to write updated config: %v", err)
|
|
}
|
|
|
|
// Verify user was added
|
|
savedData, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read saved config: %v", err)
|
|
}
|
|
|
|
var savedCfg ConfigWithAuth
|
|
if err := yaml.Unmarshal(savedData, &savedCfg); err != nil {
|
|
t.Fatalf("Failed to parse saved config: %v", err)
|
|
}
|
|
|
|
if _, exists := savedCfg.Auth.APIKeys["test_user"]; !exists {
|
|
t.Error("test_user was not added to config")
|
|
}
|
|
|
|
// Verify existing user still exists
|
|
if _, exists := savedCfg.Auth.APIKeys["existing_user"]; !exists {
|
|
t.Error("existing_user was removed from config")
|
|
}
|
|
}
|
|
|
|
func TestUserManagerListUsers(t *testing.T) {
|
|
// Create temporary config file
|
|
tempDir := t.TempDir()
|
|
configFile := filepath.Join(tempDir, "test_config.yaml")
|
|
|
|
// Initial config
|
|
config := ConfigWithAuth{
|
|
Auth: auth.AuthConfig{
|
|
Enabled: true,
|
|
APIKeys: map[auth.Username]auth.APIKeyEntry{
|
|
"admin": {
|
|
Hash: auth.APIKeyHash(auth.HashAPIKey("admin-key")),
|
|
Admin: true,
|
|
},
|
|
"regular": {
|
|
Hash: auth.APIKeyHash(auth.HashAPIKey("user-key")),
|
|
Admin: false,
|
|
},
|
|
"admin_user": {
|
|
Hash: auth.APIKeyHash(auth.HashAPIKey("adminuser-key")),
|
|
Admin: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal config: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configFile, data, 0644); err != nil {
|
|
t.Fatalf("Failed to write config: %v", err)
|
|
}
|
|
|
|
// Load and verify config
|
|
configData, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read config: %v", err)
|
|
}
|
|
|
|
var cfg ConfigWithAuth
|
|
if err := yaml.Unmarshal(configData, &cfg); err != nil {
|
|
t.Fatalf("Failed to parse config: %v", err)
|
|
}
|
|
|
|
// Test user listing
|
|
userCount := len(cfg.Auth.APIKeys)
|
|
expectedCount := 3
|
|
|
|
if userCount != expectedCount {
|
|
t.Errorf("Expected %d users, got %d", expectedCount, userCount)
|
|
}
|
|
|
|
// Verify admin detection
|
|
keyMap := map[auth.Username]string{
|
|
"admin": "admin-key",
|
|
"regular": "user-key",
|
|
"admin_user": "adminuser-key",
|
|
}
|
|
|
|
for username := range cfg.Auth.APIKeys {
|
|
testKey := keyMap[username]
|
|
|
|
user, err := cfg.Auth.ValidateAPIKey(testKey)
|
|
if err != nil {
|
|
t.Errorf("Failed to validate user %s: %v", username, err)
|
|
continue // Skip admin check if validation failed
|
|
}
|
|
|
|
expectedAdmin := username == "admin" || username == "admin_user"
|
|
if user.Admin != expectedAdmin {
|
|
t.Errorf("User %s: expected admin=%v, got admin=%v", username, expectedAdmin, user.Admin)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUserManagerHashKey(t *testing.T) {
|
|
key := "test-api-key-123"
|
|
expectedHash := auth.HashAPIKey(key)
|
|
|
|
if expectedHash == "" {
|
|
t.Error("Hash should not be empty")
|
|
}
|
|
|
|
if len(expectedHash) != 64 {
|
|
t.Errorf("Expected hash length 64, got %d", len(expectedHash))
|
|
}
|
|
|
|
// Test consistency
|
|
hash2 := auth.HashAPIKey(key)
|
|
if expectedHash != hash2 {
|
|
t.Error("Hash should be consistent")
|
|
}
|
|
}
|
|
|
|
func TestConfigPersistence(t *testing.T) {
|
|
// Create temporary config file
|
|
tempDir := t.TempDir()
|
|
configFile := filepath.Join(tempDir, "test_config.yaml")
|
|
|
|
// Create initial config
|
|
config := ConfigWithAuth{
|
|
Auth: auth.AuthConfig{
|
|
Enabled: true,
|
|
APIKeys: map[auth.Username]auth.APIKeyEntry{},
|
|
},
|
|
}
|
|
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal config: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configFile, data, 0644); err != nil {
|
|
t.Fatalf("Failed to write config: %v", err)
|
|
}
|
|
|
|
// Simulate multiple operations
|
|
operations := []struct {
|
|
username string
|
|
apiKey string
|
|
}{
|
|
{"user1", "key1"},
|
|
{"user2", "key2"},
|
|
{"admin_user", "admin-key"},
|
|
}
|
|
|
|
for _, op := range operations {
|
|
// Load config
|
|
configData, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read config: %v", err)
|
|
}
|
|
|
|
var cfg ConfigWithAuth
|
|
if err := yaml.Unmarshal(configData, &cfg); err != nil {
|
|
t.Fatalf("Failed to parse config: %v", err)
|
|
}
|
|
|
|
// Add user
|
|
if cfg.Auth.APIKeys == nil {
|
|
cfg.Auth.APIKeys = make(map[auth.Username]auth.APIKeyEntry)
|
|
}
|
|
cfg.Auth.APIKeys[auth.Username(op.username)] = auth.APIKeyEntry{
|
|
Hash: auth.APIKeyHash(auth.HashAPIKey(op.apiKey)),
|
|
Admin: strings.Contains(strings.ToLower(op.username), "admin"),
|
|
}
|
|
|
|
// Save config
|
|
updatedData, err := yaml.Marshal(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal updated config: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configFile, updatedData, 0644); err != nil {
|
|
t.Fatalf("Failed to write updated config: %v", err)
|
|
}
|
|
|
|
// Small delay to ensure file system consistency
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
|
|
// Verify final state
|
|
finalData, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read final config: %v", err)
|
|
}
|
|
|
|
var finalCfg ConfigWithAuth
|
|
if err := yaml.Unmarshal(finalData, &finalCfg); err != nil {
|
|
t.Fatalf("Failed to parse final config: %v", err)
|
|
}
|
|
|
|
if len(finalCfg.Auth.APIKeys) != len(operations) {
|
|
t.Errorf("Expected %d users, got %d", len(operations), len(finalCfg.Auth.APIKeys))
|
|
}
|
|
|
|
for _, op := range operations {
|
|
if _, exists := finalCfg.Auth.APIKeys[auth.Username(op.username)]; !exists {
|
|
t.Errorf("User %s not found in final config", op.username)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAuthDisabled(t *testing.T) {
|
|
t.Setenv("FETCH_ML_ALLOW_INSECURE_AUTH", "1")
|
|
defer t.Setenv("FETCH_ML_ALLOW_INSECURE_AUTH", "")
|
|
|
|
// Create temporary config file with auth disabled
|
|
tempDir := t.TempDir()
|
|
configFile := filepath.Join(tempDir, "test_config.yaml")
|
|
|
|
config := ConfigWithAuth{
|
|
Auth: auth.AuthConfig{
|
|
Enabled: false,
|
|
APIKeys: map[auth.Username]auth.APIKeyEntry{}, // Empty
|
|
},
|
|
}
|
|
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal config: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configFile, data, 0644); err != nil {
|
|
t.Fatalf("Failed to write config: %v", err)
|
|
}
|
|
|
|
// Load config
|
|
configData, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read config: %v", err)
|
|
}
|
|
|
|
var cfg ConfigWithAuth
|
|
if err := yaml.Unmarshal(configData, &cfg); err != nil {
|
|
t.Fatalf("Failed to parse config: %v", err)
|
|
}
|
|
|
|
// Test validation with auth disabled
|
|
user, err := cfg.Auth.ValidateAPIKey("any-key")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error with auth disabled: %v", err)
|
|
}
|
|
|
|
if user.Name != "default" {
|
|
t.Errorf("Expected default user, got %s", user.Name)
|
|
}
|
|
|
|
if !user.Admin {
|
|
t.Error("Default user should be admin")
|
|
}
|
|
}
|