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") } }