middleware-manager/cache/cache_test.go
hhftechnologies 516a06a393 Add tests and atomicize service watcher flag
Add extensive unit tests across the codebase (api/errors, cache, many models tests, util/id_normalizer and others) to improve coverage and validate behavior. Replace ServiceWatcher.isRunning bool with atomic.Bool for safer concurrent access, update Start/Stop logic and related imports, and adjust service_watcher_test accordingly. Also update traefik_fetcher_test to use atomic counters for request counting and add minor import changes (sync/atomic).
2026-02-17 16:03:23 +05:30

247 lines
4.8 KiB
Go

package cache
import (
"sync"
"testing"
"time"
)
func TestCache_SetAndGet(t *testing.T) {
c := New()
defer c.Stop()
c.Set("key1", "value1", time.Minute)
val, ok := c.Get("key1")
if !ok {
t.Fatal("expected key1 to exist")
}
if val != "value1" {
t.Errorf("Get(key1) = %v, want value1", val)
}
}
func TestCache_Get_Expired(t *testing.T) {
c := New()
defer c.Stop()
c.Set("key1", "value1", 10*time.Millisecond)
time.Sleep(20 * time.Millisecond)
_, ok := c.Get("key1")
if ok {
t.Error("expected expired key to return false")
}
}
func TestCache_GetString(t *testing.T) {
c := New()
defer c.Stop()
c.Set("str", "hello", time.Minute)
c.Set("num", 42, time.Minute)
str, ok := c.GetString("str")
if !ok || str != "hello" {
t.Errorf("GetString(str) = %q, %v; want hello, true", str, ok)
}
_, ok = c.GetString("num")
if ok {
t.Error("GetString(num) should return false for non-string value")
}
_, ok = c.GetString("missing")
if ok {
t.Error("GetString(missing) should return false")
}
}
func TestCache_GetInt(t *testing.T) {
c := New()
defer c.Stop()
c.Set("num", 42, time.Minute)
c.Set("str", "hello", time.Minute)
num, ok := c.GetInt("num")
if !ok || num != 42 {
t.Errorf("GetInt(num) = %d, %v; want 42, true", num, ok)
}
_, ok = c.GetInt("str")
if ok {
t.Error("GetInt(str) should return false for non-int value")
}
}
func TestCache_Delete(t *testing.T) {
c := New()
defer c.Stop()
c.Set("key1", "value1", time.Minute)
c.Delete("key1")
_, ok := c.Get("key1")
if ok {
t.Error("expected key1 to be deleted")
}
}
func TestCache_Clear(t *testing.T) {
c := New()
defer c.Stop()
c.Set("k1", "v1", time.Minute)
c.Set("k2", "v2", time.Minute)
c.Clear()
if c.Len() != 0 {
t.Errorf("Len() = %d after Clear, want 0", c.Len())
}
}
func TestCache_Has(t *testing.T) {
c := New()
defer c.Stop()
c.Set("exists", "val", time.Minute)
c.Set("expired", "val", 10*time.Millisecond)
time.Sleep(20 * time.Millisecond)
if !c.Has("exists") {
t.Error("Has(exists) = false, want true")
}
if c.Has("missing") {
t.Error("Has(missing) = true, want false")
}
if c.Has("expired") {
t.Error("Has(expired) = true, want false for expired key")
}
}
func TestCache_Len(t *testing.T) {
c := New()
defer c.Stop()
c.Set("k1", "v1", time.Minute)
c.Set("k2", "v2", 10*time.Millisecond)
time.Sleep(20 * time.Millisecond)
// Len includes expired items per implementation
if c.Len() != 2 {
t.Errorf("Len() = %d, want 2 (includes expired)", c.Len())
}
}
func TestCache_Keys(t *testing.T) {
c := New()
defer c.Stop()
c.Set("active1", "v1", time.Minute)
c.Set("active2", "v2", time.Minute)
c.Set("expired", "v3", 10*time.Millisecond)
time.Sleep(20 * time.Millisecond)
keys := c.Keys()
if len(keys) != 2 {
t.Errorf("Keys() returned %d keys, want 2 (non-expired only)", len(keys))
}
keySet := map[string]bool{}
for _, k := range keys {
keySet[k] = true
}
if !keySet["active1"] || !keySet["active2"] {
t.Errorf("Keys() = %v, want [active1, active2]", keys)
}
}
func TestCache_GetOrSet(t *testing.T) {
c := New()
defer c.Stop()
callCount := 0
fn := func() (interface{}, error) {
callCount++
return "computed", nil
}
// Miss — should call fn
val, err := c.GetOrSet("key", time.Minute, fn)
if err != nil {
t.Fatalf("GetOrSet error: %v", err)
}
if val != "computed" {
t.Errorf("GetOrSet = %v, want computed", val)
}
if callCount != 1 {
t.Errorf("fn called %d times, want 1", callCount)
}
// Hit — should NOT call fn again
val, err = c.GetOrSet("key", time.Minute, fn)
if err != nil {
t.Fatalf("GetOrSet error: %v", err)
}
if val != "computed" {
t.Errorf("GetOrSet = %v, want computed", val)
}
if callCount != 1 {
t.Errorf("fn called %d times on cache hit, want 1", callCount)
}
}
func TestCache_SetIfNotExists(t *testing.T) {
c := New()
defer c.Stop()
// Set when absent
ok := c.SetIfNotExists("key", "first", time.Minute)
if !ok {
t.Error("SetIfNotExists should return true when key absent")
}
// Don't overwrite existing
ok = c.SetIfNotExists("key", "second", time.Minute)
if ok {
t.Error("SetIfNotExists should return false when key exists")
}
val, _ := c.Get("key")
if val != "first" {
t.Errorf("value = %v, want first (should not be overwritten)", val)
}
// Overwrite expired
c.Set("expiring", "old", 10*time.Millisecond)
time.Sleep(20 * time.Millisecond)
ok = c.SetIfNotExists("expiring", "new", time.Minute)
if !ok {
t.Error("SetIfNotExists should return true for expired key")
}
}
func TestCache_Stop(t *testing.T) {
c := New()
// Should not panic
c.Stop()
}
func TestCache_Concurrent(t *testing.T) {
c := New()
defer c.Stop()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
key := "key"
c.Set(key, n, time.Minute)
c.Get(key)
c.Has(key)
c.Keys()
}(i)
}
wg.Wait()
}