feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
This commit is contained in:
245
internal/cache/cache_test.go
vendored
Normal file
245
internal/cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/user-management-system/internal/cache"
|
||||
)
|
||||
|
||||
// TestRedisCache_Disabled 测试禁用状态的RedisCache不报错
|
||||
func TestRedisCache_Disabled(t *testing.T) {
|
||||
c := cache.NewRedisCache(false)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := c.Set(ctx, "key", "value", time.Minute); err != nil {
|
||||
t.Errorf("disabled cache Set should not error: %v", err)
|
||||
}
|
||||
val, err := c.Get(ctx, "key")
|
||||
if err != nil {
|
||||
t.Errorf("disabled cache Get should not error: %v", err)
|
||||
}
|
||||
if val != nil {
|
||||
t.Errorf("disabled cache Get should return nil, got: %v", val)
|
||||
}
|
||||
if err := c.Delete(ctx, "key"); err != nil {
|
||||
t.Errorf("disabled cache Delete should not error: %v", err)
|
||||
}
|
||||
exists, err := c.Exists(ctx, "key")
|
||||
if err != nil {
|
||||
t.Errorf("disabled cache Exists should not error: %v", err)
|
||||
}
|
||||
if exists {
|
||||
t.Error("disabled cache Exists should return false")
|
||||
}
|
||||
if err := c.Clear(ctx); err != nil {
|
||||
t.Errorf("disabled cache Clear should not error: %v", err)
|
||||
}
|
||||
if err := c.Close(); err != nil {
|
||||
t.Errorf("disabled cache Close should not error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestL1Cache_SetGet 测试L1内存缓存的基本读写
|
||||
func TestL1Cache_SetGet(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
|
||||
l1.Set("user:1", "alice", time.Minute)
|
||||
val, ok := l1.Get("user:1")
|
||||
if !ok {
|
||||
t.Fatal("L1 Get: expected hit")
|
||||
}
|
||||
if val != "alice" {
|
||||
t.Errorf("L1 Get value = %v, want alice", val)
|
||||
}
|
||||
}
|
||||
|
||||
// TestL1Cache_Expiration 测试L1缓存过期
|
||||
func TestL1Cache_Expiration(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
|
||||
l1.Set("expire:1", "v", 50*time.Millisecond)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
_, ok := l1.Get("expire:1")
|
||||
if ok {
|
||||
t.Error("L1 key should have expired")
|
||||
}
|
||||
}
|
||||
|
||||
// TestL1Cache_Delete 测试L1缓存删除
|
||||
func TestL1Cache_Delete(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
|
||||
l1.Set("del:1", "v", time.Minute)
|
||||
l1.Delete("del:1")
|
||||
|
||||
_, ok := l1.Get("del:1")
|
||||
if ok {
|
||||
t.Error("L1 key should be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
// TestL1Cache_Clear 测试L1缓存清空
|
||||
func TestL1Cache_Clear(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
|
||||
l1.Set("a", 1, time.Minute)
|
||||
l1.Set("b", 2, time.Minute)
|
||||
l1.Clear()
|
||||
|
||||
_, ok1 := l1.Get("a")
|
||||
_, ok2 := l1.Get("b")
|
||||
if ok1 || ok2 {
|
||||
t.Error("L1 cache should be empty after Clear()")
|
||||
}
|
||||
}
|
||||
|
||||
// TestL1Cache_Size 测试L1缓存大小统计
|
||||
func TestL1Cache_Size(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
|
||||
l1.Set("s1", 1, time.Minute)
|
||||
l1.Set("s2", 2, time.Minute)
|
||||
l1.Set("s3", 3, time.Minute)
|
||||
|
||||
if l1.Size() != 3 {
|
||||
t.Errorf("L1 Size = %d, want 3", l1.Size())
|
||||
}
|
||||
|
||||
l1.Delete("s1")
|
||||
if l1.Size() != 2 {
|
||||
t.Errorf("L1 Size after Delete = %d, want 2", l1.Size())
|
||||
}
|
||||
}
|
||||
|
||||
// TestL1Cache_Cleanup 测试L1过期键清理
|
||||
func TestL1Cache_Cleanup(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
|
||||
l1.Set("exp", "v", 30*time.Millisecond)
|
||||
l1.Set("keep", "v", time.Minute)
|
||||
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
l1.Cleanup()
|
||||
|
||||
if l1.Size() != 1 {
|
||||
t.Errorf("after Cleanup L1 Size = %d, want 1", l1.Size())
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheManager_SetGet 测试CacheManager读写(仅L1)
|
||||
func TestCacheManager_SetGet(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
cm := cache.NewCacheManager(l1, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := cm.Set(ctx, "k1", "v1", time.Minute, time.Minute); err != nil {
|
||||
t.Fatalf("CacheManager Set error: %v", err)
|
||||
}
|
||||
val, ok := cm.Get(ctx, "k1")
|
||||
if !ok {
|
||||
t.Fatal("CacheManager Get: expected hit")
|
||||
}
|
||||
if val != "v1" {
|
||||
t.Errorf("CacheManager Get value = %v, want v1", val)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheManager_Delete 测试CacheManager删除
|
||||
func TestCacheManager_Delete(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
cm := cache.NewCacheManager(l1, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
_ = cm.Set(ctx, "del:1", "v", time.Minute, time.Minute)
|
||||
if err := cm.Delete(ctx, "del:1"); err != nil {
|
||||
t.Fatalf("CacheManager Delete error: %v", err)
|
||||
}
|
||||
_, ok := cm.Get(ctx, "del:1")
|
||||
if ok {
|
||||
t.Error("CacheManager key should be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheManager_Exists 测试CacheManager存在性检查
|
||||
func TestCacheManager_Exists(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
cm := cache.NewCacheManager(l1, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
if cm.Exists(ctx, "notexist") {
|
||||
t.Error("CacheManager Exists should return false for missing key")
|
||||
}
|
||||
_ = cm.Set(ctx, "exist:1", "v", time.Minute, time.Minute)
|
||||
if !cm.Exists(ctx, "exist:1") {
|
||||
t.Error("CacheManager Exists should return true after Set")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheManager_Clear 测试CacheManager清空
|
||||
func TestCacheManager_Clear(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
cm := cache.NewCacheManager(l1, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
_ = cm.Set(ctx, "a", 1, time.Minute, time.Minute)
|
||||
_ = cm.Set(ctx, "b", 2, time.Minute, time.Minute)
|
||||
|
||||
if err := cm.Clear(ctx); err != nil {
|
||||
t.Fatalf("CacheManager Clear error: %v", err)
|
||||
}
|
||||
if cm.Exists(ctx, "a") || cm.Exists(ctx, "b") {
|
||||
t.Error("CacheManager should be empty after Clear()")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheManager_Concurrent 测试CacheManager并发安全
|
||||
func TestCacheManager_Concurrent(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
cm := cache.NewCacheManager(l1, nil)
|
||||
ctx := context.Background()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var hitCount int64
|
||||
|
||||
// 预热
|
||||
_ = cm.Set(ctx, "concurrent:key", "v", time.Minute, time.Minute)
|
||||
|
||||
// 并发读写
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 20; j++ {
|
||||
if _, ok := cm.Get(ctx, "concurrent:key"); ok {
|
||||
atomic.AddInt64(&hitCount, 1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if hitCount == 0 {
|
||||
t.Error("concurrent cache reads should produce hits")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheManager_WithDisabledL2 测试CacheManager配合禁用L2
|
||||
func TestCacheManager_WithDisabledL2(t *testing.T) {
|
||||
l1 := cache.NewL1Cache()
|
||||
l2 := cache.NewRedisCache(false) // disabled
|
||||
cm := cache.NewCacheManager(l1, l2)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := cm.Set(ctx, "k", "v", time.Minute, time.Minute); err != nil {
|
||||
t.Fatalf("Set with disabled L2 should not error: %v", err)
|
||||
}
|
||||
val, ok := cm.Get(ctx, "k")
|
||||
if !ok || val != "v" {
|
||||
t.Errorf("Get from L1 after Set = (%v, %v), want (v, true)", val, ok)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user