//go:build integration // cache_integration_test.go - 缓存集成测试(L2 层,进程内) // // 测试代理缓存的核心功能。 // // 作者:xfy package integration import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "rua.plus/lolly/internal/cache" ) // TestProxyCacheCreation 测试代理缓存创建 func TestProxyCacheCreation(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/api/", Methods: []string{"GET", "HEAD"}, Statuses: []int{200, 301, 302}, MaxAge: 60 * time.Second, }, } pc := cache.NewProxyCache(rules, true, 30*time.Second, 0, 0) require.NotNil(t, pc) } // TestProxyCacheDisabled 测试禁用缓存(空规则) func TestProxyCacheDisabled(t *testing.T) { rules := []cache.ProxyCacheRule{} pc := cache.NewProxyCache(rules, false, 0, 0, 0) require.NotNil(t, pc) } // TestProxyCacheSetAndGet 测试缓存存取 func TestProxyCacheSetAndGet(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/", Methods: []string{"GET"}, Statuses: []int{200}, MaxAge: 60 * time.Second, }, } pc := cache.NewProxyCache(rules, true, 30*time.Second, 0, 0) require.NotNil(t, pc) origKey := "GET:/test" hashKey := cache.HashPathWithMethod("/test", "GET") body := []byte("test response body") headers := map[string]string{ "Content-Type": "application/json", "X-Custom": "value", } // 存入缓存 pc.Set(hashKey, origKey, body, headers, 200, 60*time.Second) // 从缓存获取 entry, found, stale := pc.Get(hashKey, origKey) assert.True(t, found) assert.False(t, stale) require.NotNil(t, entry) assert.Equal(t, 200, entry.Status) assert.Equal(t, body, entry.Data) assert.Equal(t, "application/json", entry.Headers["Content-Type"]) } // TestProxyCacheMiss 测试缓存未命中 func TestProxyCacheMiss(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/", Methods: []string{"GET"}, Statuses: []int{200}, MaxAge: 60 * time.Second, }, } pc := cache.NewProxyCache(rules, true, 30*time.Second, 0, 0) require.NotNil(t, pc) origKey := "GET:/nonexistent" hashKey := cache.HashPathWithMethod("/nonexistent", "GET") // 不存在的键应返回未命中 entry, found, stale := pc.Get(hashKey, origKey) assert.False(t, found) assert.False(t, stale) assert.Nil(t, entry) } // TestProxyCacheExpiration 测试缓存过期 func TestProxyCacheExpiration(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/", Methods: []string{"GET"}, Statuses: []int{200}, MaxAge: 100 * time.Millisecond, // 短过期时间 }, } pc := cache.NewProxyCache(rules, true, 50*time.Millisecond, 0, 0) require.NotNil(t, pc) origKey := "GET:/expiring" hashKey := cache.HashPathWithMethod("/expiring", "GET") body := []byte("will expire") // 存入缓存 pc.Set(hashKey, origKey, body, nil, 200, 100*time.Millisecond) // 立即获取应该命中 entry, found, _ := pc.Get(hashKey, origKey) assert.True(t, found) assert.NotNil(t, entry) // 等待过期 time.Sleep(150 * time.Millisecond) // 过期后应该未命中 entry, found, stale := pc.Get(hashKey, origKey) assert.False(t, found) assert.False(t, stale) assert.Nil(t, entry) } // TestProxyCacheStale 测试过期缓存复用 func TestProxyCacheStale(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/", Methods: []string{"GET"}, Statuses: []int{200}, MaxAge: 100 * time.Millisecond, }, } // staleTime = 200ms,允许过期后 200ms 内复用 pc := cache.NewProxyCache(rules, true, 200*time.Millisecond, 0, 0) require.NotNil(t, pc) origKey := "GET:/stale" hashKey := cache.HashPathWithMethod("/stale", "GET") body := []byte("stale data") // 存入缓存 pc.Set(hashKey, origKey, body, nil, 200, 100*time.Millisecond) // 等待过期但仍在 stale 时间内 time.Sleep(150 * time.Millisecond) // 应该返回 stale 数据 entry, found, stale := pc.Get(hashKey, origKey) assert.True(t, found) assert.True(t, stale) assert.NotNil(t, entry) assert.Equal(t, body, entry.Data) } // TestProxyCacheHashKeyCollision 测试哈希碰撞检测 func TestProxyCacheHashKeyCollision(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/", Methods: []string{"GET"}, Statuses: []int{200}, MaxAge: 60 * time.Second, }, } pc := cache.NewProxyCache(rules, true, 30*time.Second, 0, 0) require.NotNil(t, pc) // 两个不同的 key key1 := "GET:/resource/1" key2 := "GET:/resource/2" hashKey1 := cache.HashPathWithMethod("/resource/1", "GET") hashKey2 := cache.HashPathWithMethod("/resource/2", "GET") // 存入不同的数据 pc.Set(hashKey1, key1, []byte("data1"), nil, 200, 60*time.Second) pc.Set(hashKey2, key2, []byte("data2"), nil, 200, 60*time.Second) // 验证各自返回正确的数据 entry1, found1, _ := pc.Get(hashKey1, key1) assert.True(t, found1) assert.Equal(t, []byte("data1"), entry1.Data) entry2, found2, _ := pc.Get(hashKey2, key2) assert.True(t, found2) assert.Equal(t, []byte("data2"), entry2.Data) } // TestProxyCacheConcurrent 测试并发访问 func TestProxyCacheConcurrent(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/", Methods: []string{"GET"}, Statuses: []int{200}, MaxAge: 60 * time.Second, }, } pc := cache.NewProxyCache(rules, true, 30*time.Second, 0, 0) require.NotNil(t, pc) // 并发写入 done := make(chan bool, 10) for i := 0; i < 10; i++ { go func(idx int) { key := "GET:/concurrent" hashKey := cache.HashPathWithMethod("/concurrent", "GET") pc.Set(hashKey, key, []byte("concurrent data"), nil, 200, 60*time.Second) done <- true }(i) } // 等待所有写入完成 for i := 0; i < 10; i++ { <-done } // 验证数据一致性 key := "GET:/concurrent" hashKey := cache.HashPathWithMethod("/concurrent", "GET") entry, found, _ := pc.Get(hashKey, key) assert.True(t, found) assert.NotNil(t, entry) } // TestProxyCacheUsesCount 测试访问计数 func TestProxyCacheUsesCount(t *testing.T) { rules := []cache.ProxyCacheRule{ { Path: "/", Methods: []string{"GET"}, Statuses: []int{200}, MaxAge: 60 * time.Second, }, } pc := cache.NewProxyCache(rules, true, 30*time.Second, 0, 0) require.NotNil(t, pc) origKey := "GET:/counted" hashKey := cache.HashPathWithMethod("/counted", "GET") // 存入缓存 pc.Set(hashKey, origKey, []byte("data"), nil, 200, 60*time.Second) // 多次访问 for i := 0; i < 5; i++ { entry, found, _ := pc.Get(hashKey, origKey) assert.True(t, found) require.NotNil(t, entry) } // 验证访问计数 entry, _, _ := pc.Get(hashKey, origKey) require.NotNil(t, entry) // Uses 计数应该是 6(5次循环 + 1次验证) assert.GreaterOrEqual(t, entry.Uses.Load(), int32(6)) }