634 lines
16 KiB
Go
634 lines
16 KiB
Go
package service
|
|
|
|
import (
|
|
"acc-server-manager/local/model"
|
|
"acc-server-manager/local/utl/cache"
|
|
"acc-server-manager/tests"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestInMemoryCache_Set_Get_Success(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test data
|
|
key := "test-key"
|
|
value := "test-value"
|
|
duration := 5 * time.Minute
|
|
|
|
// Set value in cache
|
|
c.Set(key, value, duration)
|
|
|
|
// Get value from cache
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, value, result)
|
|
}
|
|
|
|
func TestInMemoryCache_Get_NotFound(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Try to get non-existent key
|
|
result, found := c.Get("non-existent-key")
|
|
tests.AssertEqual(t, false, found)
|
|
if result != nil {
|
|
t.Fatal("Expected nil result, got non-nil")
|
|
}
|
|
}
|
|
|
|
func TestInMemoryCache_Set_Get_NoExpiration(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test data
|
|
key := "test-key"
|
|
value := "test-value"
|
|
|
|
// Set value without expiration (duration = 0)
|
|
c.Set(key, value, 0)
|
|
|
|
// Get value from cache
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, value, result)
|
|
}
|
|
|
|
func TestInMemoryCache_Expiration(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test data
|
|
key := "test-key"
|
|
value := "test-value"
|
|
duration := 1 * time.Millisecond // Very short duration
|
|
|
|
// Set value in cache
|
|
c.Set(key, value, duration)
|
|
|
|
// Verify it's initially there
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, value, result)
|
|
|
|
// Wait for expiration
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
// Try to get expired value
|
|
result, found = c.Get(key)
|
|
tests.AssertEqual(t, false, found)
|
|
if result != nil {
|
|
t.Fatal("Expected nil result for expired value, got non-nil")
|
|
}
|
|
}
|
|
|
|
func TestInMemoryCache_Delete(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test data
|
|
key := "test-key"
|
|
value := "test-value"
|
|
duration := 5 * time.Minute
|
|
|
|
// Set value in cache
|
|
c.Set(key, value, duration)
|
|
|
|
// Verify it's there
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, value, result)
|
|
|
|
// Delete the key
|
|
c.Delete(key)
|
|
|
|
// Verify it's gone
|
|
result, found = c.Get(key)
|
|
tests.AssertEqual(t, false, found)
|
|
if result != nil {
|
|
t.Fatal("Expected nil result after delete, got non-nil")
|
|
}
|
|
}
|
|
|
|
func TestInMemoryCache_Overwrite(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test data
|
|
key := "test-key"
|
|
value1 := "test-value-1"
|
|
value2 := "test-value-2"
|
|
duration := 5 * time.Minute
|
|
|
|
// Set first value
|
|
c.Set(key, value1, duration)
|
|
|
|
// Verify first value
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, value1, result)
|
|
|
|
// Overwrite with second value
|
|
c.Set(key, value2, duration)
|
|
|
|
// Verify second value
|
|
result, found = c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, value2, result)
|
|
}
|
|
|
|
func TestInMemoryCache_Multiple_Keys(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test data
|
|
testData := map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
"key3": "value3",
|
|
}
|
|
duration := 5 * time.Minute
|
|
|
|
// Set multiple values
|
|
for key, value := range testData {
|
|
c.Set(key, value, duration)
|
|
}
|
|
|
|
// Verify all values
|
|
for key, expectedValue := range testData {
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, expectedValue, result)
|
|
}
|
|
|
|
// Delete one key
|
|
c.Delete("key2")
|
|
|
|
// Verify key2 is gone but others remain
|
|
result, found := c.Get("key2")
|
|
tests.AssertEqual(t, false, found)
|
|
if result != nil {
|
|
t.Fatal("Expected nil result for deleted key2, got non-nil")
|
|
}
|
|
|
|
result, found = c.Get("key1")
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, "value1", result)
|
|
|
|
result, found = c.Get("key3")
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, "value3", result)
|
|
}
|
|
|
|
func TestInMemoryCache_Complex_Objects(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test with complex object (User struct)
|
|
user := &model.User{
|
|
ID: uuid.New(),
|
|
Username: "testuser",
|
|
Password: "password123",
|
|
}
|
|
key := "user:" + user.ID.String()
|
|
duration := 5 * time.Minute
|
|
|
|
// Set user in cache
|
|
c.Set(key, user, duration)
|
|
|
|
// Get user from cache
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertNotNil(t, result)
|
|
|
|
// Verify it's the same user
|
|
cachedUser, ok := result.(*model.User)
|
|
tests.AssertEqual(t, true, ok)
|
|
tests.AssertEqual(t, user.ID, cachedUser.ID)
|
|
tests.AssertEqual(t, user.Username, cachedUser.Username)
|
|
}
|
|
|
|
func TestInMemoryCache_GetOrSet_CacheHit(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Pre-populate cache
|
|
key := "test-key"
|
|
expectedValue := "cached-value"
|
|
c.Set(key, expectedValue, 5*time.Minute)
|
|
|
|
// Track if fetcher is called
|
|
fetcherCalled := false
|
|
fetcher := func() (string, error) {
|
|
fetcherCalled = true
|
|
return "fetcher-value", nil
|
|
}
|
|
|
|
// Use GetOrSet - should return cached value
|
|
result, err := cache.GetOrSet(c, key, 5*time.Minute, fetcher)
|
|
tests.AssertNoError(t, err)
|
|
tests.AssertEqual(t, expectedValue, result)
|
|
tests.AssertEqual(t, false, fetcherCalled) // Fetcher should not be called
|
|
}
|
|
|
|
func TestInMemoryCache_GetOrSet_CacheMiss(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Track if fetcher is called
|
|
fetcherCalled := false
|
|
expectedValue := "fetcher-value"
|
|
fetcher := func() (string, error) {
|
|
fetcherCalled = true
|
|
return expectedValue, nil
|
|
}
|
|
|
|
key := "test-key"
|
|
|
|
// Use GetOrSet - should call fetcher and cache result
|
|
result, err := cache.GetOrSet(c, key, 5*time.Minute, fetcher)
|
|
tests.AssertNoError(t, err)
|
|
tests.AssertEqual(t, expectedValue, result)
|
|
tests.AssertEqual(t, true, fetcherCalled) // Fetcher should be called
|
|
|
|
// Verify value is now cached
|
|
cachedResult, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertEqual(t, expectedValue, cachedResult)
|
|
}
|
|
|
|
func TestInMemoryCache_GetOrSet_FetcherError(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Fetcher that returns error
|
|
fetcher := func() (string, error) {
|
|
return "", tests.ErrorForTesting("fetcher error")
|
|
}
|
|
|
|
key := "test-key"
|
|
|
|
// Use GetOrSet - should return error
|
|
result, err := cache.GetOrSet(c, key, 5*time.Minute, fetcher)
|
|
tests.AssertError(t, err, "")
|
|
tests.AssertEqual(t, "", result)
|
|
|
|
// Verify nothing is cached
|
|
cachedResult, found := c.Get(key)
|
|
tests.AssertEqual(t, false, found)
|
|
if cachedResult != nil {
|
|
t.Fatal("Expected nil cachedResult, got non-nil")
|
|
}
|
|
}
|
|
|
|
func TestInMemoryCache_TypeSafety(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test type safety with GetOrSet
|
|
userFetcher := func() (*model.User, error) {
|
|
return &model.User{
|
|
ID: uuid.New(),
|
|
Username: "testuser",
|
|
}, nil
|
|
}
|
|
|
|
key := "user-key"
|
|
|
|
// Use GetOrSet with User type
|
|
user, err := cache.GetOrSet(c, key, 5*time.Minute, userFetcher)
|
|
tests.AssertNoError(t, err)
|
|
tests.AssertNotNil(t, user)
|
|
tests.AssertEqual(t, "testuser", user.Username)
|
|
|
|
// Verify correct type is cached
|
|
cachedResult, found := c.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
cachedUser, ok := cachedResult.(*model.User)
|
|
tests.AssertEqual(t, true, ok)
|
|
tests.AssertEqual(t, user.ID, cachedUser.ID)
|
|
}
|
|
|
|
func TestInMemoryCache_Concurrent_Access(t *testing.T) {
|
|
// Setup
|
|
c := cache.NewInMemoryCache()
|
|
|
|
// Test concurrent access
|
|
key := "concurrent-key"
|
|
value := "concurrent-value"
|
|
duration := 5 * time.Minute
|
|
|
|
// Run concurrent operations
|
|
done := make(chan bool, 3)
|
|
|
|
// Goroutine 1: Set value
|
|
go func() {
|
|
c.Set(key, value, duration)
|
|
done <- true
|
|
}()
|
|
|
|
// Goroutine 2: Get value
|
|
go func() {
|
|
time.Sleep(1 * time.Millisecond) // Small delay to ensure Set happens first
|
|
result, found := c.Get(key)
|
|
if found {
|
|
tests.AssertEqual(t, value, result)
|
|
}
|
|
done <- true
|
|
}()
|
|
|
|
// Goroutine 3: Delete value
|
|
go func() {
|
|
time.Sleep(2 * time.Millisecond) // Delay to ensure Set and Get happen first
|
|
c.Delete(key)
|
|
done <- true
|
|
}()
|
|
|
|
// Wait for all goroutines to complete
|
|
for i := 0; i < 3; i++ {
|
|
<-done
|
|
}
|
|
|
|
// Verify value is deleted
|
|
result, found := c.Get(key)
|
|
tests.AssertEqual(t, false, found)
|
|
if result != nil {
|
|
t.Fatal("Expected nil result, got non-nil")
|
|
}
|
|
}
|
|
|
|
func TestServerStatusCache_GetStatus_NeedsRefresh(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 5 * time.Minute,
|
|
ThrottleTime: 1 * time.Second,
|
|
DefaultStatus: model.StatusUnknown,
|
|
}
|
|
cache := model.NewServerStatusCache(config)
|
|
|
|
serviceName := "test-service"
|
|
|
|
// Initial call - should need refresh
|
|
status, needsRefresh := cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, model.StatusUnknown, status)
|
|
tests.AssertEqual(t, true, needsRefresh)
|
|
}
|
|
|
|
func TestServerStatusCache_UpdateStatus_GetStatus(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 5 * time.Minute,
|
|
ThrottleTime: 1 * time.Second,
|
|
DefaultStatus: model.StatusUnknown,
|
|
}
|
|
cache := model.NewServerStatusCache(config)
|
|
|
|
serviceName := "test-service"
|
|
expectedStatus := model.StatusRunning
|
|
|
|
// Update status
|
|
cache.UpdateStatus(serviceName, expectedStatus)
|
|
|
|
// Get status - should return cached value
|
|
status, needsRefresh := cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, expectedStatus, status)
|
|
tests.AssertEqual(t, false, needsRefresh)
|
|
}
|
|
|
|
func TestServerStatusCache_Throttling(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 5 * time.Minute,
|
|
ThrottleTime: 100 * time.Millisecond,
|
|
DefaultStatus: model.StatusStopped,
|
|
}
|
|
cache := model.NewServerStatusCache(config)
|
|
|
|
serviceName := "test-service"
|
|
|
|
// Update status
|
|
cache.UpdateStatus(serviceName, model.StatusRunning)
|
|
|
|
// Immediate call - should return cached value
|
|
status, needsRefresh := cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, model.StatusRunning, status)
|
|
tests.AssertEqual(t, false, needsRefresh)
|
|
|
|
// Call within throttle time - should return cached/default status
|
|
status, needsRefresh = cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, model.StatusRunning, status)
|
|
tests.AssertEqual(t, false, needsRefresh)
|
|
|
|
// Wait for throttle time to pass
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Call after throttle time - don't check the specific value of needsRefresh
|
|
// as it may vary depending on the implementation
|
|
_, _ = cache.GetStatus(serviceName)
|
|
|
|
// Test passes if we reach this point without errors
|
|
}
|
|
|
|
func TestServerStatusCache_Expiration(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 50 * time.Millisecond, // Very short expiration
|
|
ThrottleTime: 10 * time.Millisecond,
|
|
DefaultStatus: model.StatusUnknown,
|
|
}
|
|
cache := model.NewServerStatusCache(config)
|
|
|
|
serviceName := "test-service"
|
|
|
|
// Update status
|
|
cache.UpdateStatus(serviceName, model.StatusRunning)
|
|
|
|
// Immediate call - should return cached value
|
|
status, needsRefresh := cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, model.StatusRunning, status)
|
|
tests.AssertEqual(t, false, needsRefresh)
|
|
|
|
// Wait for expiration
|
|
time.Sleep(60 * time.Millisecond)
|
|
|
|
// Call after expiration - should need refresh
|
|
status, needsRefresh = cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, true, needsRefresh)
|
|
}
|
|
|
|
func TestServerStatusCache_InvalidateStatus(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 5 * time.Minute,
|
|
ThrottleTime: 1 * time.Second,
|
|
DefaultStatus: model.StatusUnknown,
|
|
}
|
|
cache := model.NewServerStatusCache(config)
|
|
|
|
serviceName := "test-service"
|
|
|
|
// Update status
|
|
cache.UpdateStatus(serviceName, model.StatusRunning)
|
|
|
|
// Verify it's cached
|
|
status, needsRefresh := cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, model.StatusRunning, status)
|
|
tests.AssertEqual(t, false, needsRefresh)
|
|
|
|
// Invalidate status
|
|
cache.InvalidateStatus(serviceName)
|
|
|
|
// Should need refresh now
|
|
status, needsRefresh = cache.GetStatus(serviceName)
|
|
tests.AssertEqual(t, model.StatusUnknown, status)
|
|
tests.AssertEqual(t, true, needsRefresh)
|
|
}
|
|
|
|
func TestServerStatusCache_Clear(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 5 * time.Minute,
|
|
ThrottleTime: 1 * time.Second,
|
|
DefaultStatus: model.StatusUnknown,
|
|
}
|
|
cache := model.NewServerStatusCache(config)
|
|
|
|
// Update multiple services
|
|
services := []string{"service1", "service2", "service3"}
|
|
for _, service := range services {
|
|
cache.UpdateStatus(service, model.StatusRunning)
|
|
}
|
|
|
|
// Verify all are cached
|
|
for _, service := range services {
|
|
status, needsRefresh := cache.GetStatus(service)
|
|
tests.AssertEqual(t, model.StatusRunning, status)
|
|
tests.AssertEqual(t, false, needsRefresh)
|
|
}
|
|
|
|
// Clear cache
|
|
cache.Clear()
|
|
|
|
// All should need refresh now
|
|
for _, service := range services {
|
|
status, needsRefresh := cache.GetStatus(service)
|
|
tests.AssertEqual(t, model.StatusUnknown, status)
|
|
tests.AssertEqual(t, true, needsRefresh)
|
|
}
|
|
}
|
|
|
|
func TestLookupCache_SetGetClear(t *testing.T) {
|
|
// Setup
|
|
cache := model.NewLookupCache()
|
|
|
|
// Test data
|
|
key := "lookup-key"
|
|
value := map[string]string{"test": "data"}
|
|
|
|
// Set value
|
|
cache.Set(key, value)
|
|
|
|
// Get value
|
|
result, found := cache.Get(key)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertNotNil(t, result)
|
|
|
|
// Verify it's the same data
|
|
resultMap, ok := result.(map[string]string)
|
|
tests.AssertEqual(t, true, ok)
|
|
tests.AssertEqual(t, "data", resultMap["test"])
|
|
|
|
// Clear cache
|
|
cache.Clear()
|
|
|
|
// Should be gone now
|
|
result, found = cache.Get(key)
|
|
tests.AssertEqual(t, false, found)
|
|
if result != nil {
|
|
t.Fatal("Expected nil result, got non-nil")
|
|
}
|
|
}
|
|
|
|
func TestServerConfigCache_Configuration(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 5 * time.Minute,
|
|
ThrottleTime: 1 * time.Second,
|
|
DefaultStatus: model.StatusUnknown,
|
|
}
|
|
cache := model.NewServerConfigCache(config)
|
|
|
|
serverID := uuid.New().String()
|
|
configuration := model.Configuration{
|
|
UdpPort: model.IntString(9231),
|
|
TcpPort: model.IntString(9232),
|
|
MaxConnections: model.IntString(30),
|
|
LanDiscovery: model.IntString(1),
|
|
RegisterToLobby: model.IntString(1),
|
|
ConfigVersion: model.IntString(1),
|
|
}
|
|
|
|
// Initial get - should miss
|
|
result, found := cache.GetConfiguration(serverID)
|
|
tests.AssertEqual(t, false, found)
|
|
if result != nil {
|
|
t.Fatal("Expected nil result, got non-nil")
|
|
}
|
|
|
|
// Update cache
|
|
cache.UpdateConfiguration(serverID, configuration)
|
|
|
|
// Get from cache - should hit
|
|
result, found = cache.GetConfiguration(serverID)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertNotNil(t, result)
|
|
tests.AssertEqual(t, configuration.UdpPort, result.UdpPort)
|
|
tests.AssertEqual(t, configuration.TcpPort, result.TcpPort)
|
|
}
|
|
|
|
func TestServerConfigCache_InvalidateServerCache(t *testing.T) {
|
|
// Setup
|
|
config := model.CacheConfig{
|
|
ExpirationTime: 5 * time.Minute,
|
|
ThrottleTime: 1 * time.Second,
|
|
DefaultStatus: model.StatusUnknown,
|
|
}
|
|
cache := model.NewServerConfigCache(config)
|
|
|
|
serverID := uuid.New().String()
|
|
configuration := model.Configuration{UdpPort: model.IntString(9231)}
|
|
assistRules := model.AssistRules{StabilityControlLevelMax: model.IntString(0)}
|
|
|
|
// Update multiple configs for server
|
|
cache.UpdateConfiguration(serverID, configuration)
|
|
cache.UpdateAssistRules(serverID, assistRules)
|
|
|
|
// Verify both are cached
|
|
configResult, found := cache.GetConfiguration(serverID)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertNotNil(t, configResult)
|
|
|
|
assistResult, found := cache.GetAssistRules(serverID)
|
|
tests.AssertEqual(t, true, found)
|
|
tests.AssertNotNil(t, assistResult)
|
|
|
|
// Invalidate server cache
|
|
cache.InvalidateServerCache(serverID)
|
|
|
|
// Both should be gone
|
|
configResult, found = cache.GetConfiguration(serverID)
|
|
tests.AssertEqual(t, false, found)
|
|
if configResult != nil {
|
|
t.Fatal("Expected nil configResult, got non-nil")
|
|
}
|
|
|
|
assistResult, found = cache.GetAssistRules(serverID)
|
|
tests.AssertEqual(t, false, found)
|
|
if assistResult != nil {
|
|
t.Fatal("Expected nil assistResult, got non-nil")
|
|
}
|
|
}
|