test(cache): 添加 stale_if_error 和 stale_if_timeout 测试
覆盖 ProxyCache、DiskCache、TieredCache 的 GetStale 方法, 测试场景包括:错误时可用、超时时可用、窗口过期不可用、未过期直接返回。 同步更新 NewProxyCache 调用签名。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
be974b2e18
commit
0de153bb24
6
internal/cache/cache_bench_test.go
vendored
6
internal/cache/cache_bench_test.go
vendored
@ -194,7 +194,7 @@ func BenchmarkFileCacheLRUTouch(b *testing.B) {
|
|||||||
|
|
||||||
// BenchmarkProxyCacheGet 测试代理缓存 Get 性能。
|
// BenchmarkProxyCacheGet 测试代理缓存 Get 性能。
|
||||||
func BenchmarkProxyCacheGet(b *testing.B) {
|
func BenchmarkProxyCacheGet(b *testing.B) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
|
|
||||||
// 预填充缓存
|
// 预填充缓存
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
@ -219,7 +219,7 @@ func BenchmarkProxyCacheGet(b *testing.B) {
|
|||||||
|
|
||||||
// BenchmarkProxyCacheSet 测试代理缓存 Set 性能。
|
// BenchmarkProxyCacheSet 测试代理缓存 Set 性能。
|
||||||
func BenchmarkProxyCacheSet(b *testing.B) {
|
func BenchmarkProxyCacheSet(b *testing.B) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
data := []byte("response body")
|
data := []byte("response body")
|
||||||
headers := map[string]string{"Content-Type": "application/json"}
|
headers := map[string]string{"Content-Type": "application/json"}
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ func BenchmarkProxyCacheSet(b *testing.B) {
|
|||||||
// BenchmarkProxyCacheConcurrent 测试代理缓存并发混合负载。
|
// BenchmarkProxyCacheConcurrent 测试代理缓存并发混合负载。
|
||||||
// 使用 90% Get / 10% Set 的混合负载。
|
// 使用 90% Get / 10% Set 的混合负载。
|
||||||
func BenchmarkProxyCacheConcurrent(b *testing.B) {
|
func BenchmarkProxyCacheConcurrent(b *testing.B) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
|
|
||||||
// 预填充缓存
|
// 预填充缓存
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
|
|||||||
116
internal/cache/cache_test.go
vendored
116
internal/cache/cache_test.go
vendored
@ -89,7 +89,7 @@ func TestFileCacheLRUEviction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAcquireLockWithTimeout(t *testing.T) {
|
func TestAcquireLockWithTimeout(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, true, 0)
|
pc := NewProxyCache(nil, true, 0, 0, 0)
|
||||||
key := hashKey("timeout-test")
|
key := hashKey("timeout-test")
|
||||||
|
|
||||||
// 测试获取锁
|
// 测试获取锁
|
||||||
@ -114,7 +114,7 @@ func TestAcquireLockWithTimeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRefreshTTL(t *testing.T) {
|
func TestRefreshTTL(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
key := hashKey("refresh-test")
|
key := hashKey("refresh-test")
|
||||||
origKey := "refresh-test"
|
origKey := "refresh-test"
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ func TestRefreshTTL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetValidationHeaders(t *testing.T) {
|
func TestSetValidationHeaders(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
key := hashKey("validation-test")
|
key := hashKey("validation-test")
|
||||||
origKey := "validation-test"
|
origKey := "validation-test"
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ func TestMatchRulePathVariants(t *testing.T) {
|
|||||||
rules := []ProxyCacheRule{
|
rules := []ProxyCacheRule{
|
||||||
{Path: tt.rulePath, Methods: []string{"GET"}, MaxAge: time.Minute},
|
{Path: tt.rulePath, Methods: []string{"GET"}, MaxAge: time.Minute},
|
||||||
}
|
}
|
||||||
pc := NewProxyCache(rules, false, 0)
|
pc := NewProxyCache(rules, false, 0, 0, 0)
|
||||||
rule := pc.MatchRule(tt.reqPath, "GET", 0)
|
rule := pc.MatchRule(tt.reqPath, "GET", 0)
|
||||||
if (rule != nil) != tt.want {
|
if (rule != nil) != tt.want {
|
||||||
t.Errorf("MatchRule(%s, %s) = %v, want %v", tt.rulePath, tt.reqPath, rule != nil, tt.want)
|
t.Errorf("MatchRule(%s, %s) = %v, want %v", tt.rulePath, tt.reqPath, rule != nil, tt.want)
|
||||||
@ -192,7 +192,7 @@ func TestMatchRulePathVariants(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMinUsesThreshold(t *testing.T) {
|
func TestMinUsesThreshold(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
key := hashKey("minuses-test")
|
key := hashKey("minuses-test")
|
||||||
origKey := "minuses-test"
|
origKey := "minuses-test"
|
||||||
|
|
||||||
@ -283,14 +283,14 @@ func TestNewProxyCache(t *testing.T) {
|
|||||||
{Path: "/api/", Methods: []string{"GET"}, MaxAge: 10 * time.Minute},
|
{Path: "/api/", Methods: []string{"GET"}, MaxAge: 10 * time.Minute},
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := NewProxyCache(rules, true, 60*time.Second)
|
pc := NewProxyCache(rules, true, 60*time.Second, 0, 0)
|
||||||
if pc == nil {
|
if pc == nil {
|
||||||
t.Error("Expected non-nil ProxyCache")
|
t.Error("Expected non-nil ProxyCache")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyCacheSetGet(t *testing.T) {
|
func TestProxyCacheSetGet(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
|
|
||||||
key := "test-key"
|
key := "test-key"
|
||||||
data := []byte("response body")
|
data := []byte("response body")
|
||||||
@ -314,7 +314,7 @@ func TestProxyCacheSetGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyCacheExpiration(t *testing.T) {
|
func TestProxyCacheExpiration(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
|
|
||||||
key := "expire-test"
|
key := "expire-test"
|
||||||
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
@ -335,7 +335,7 @@ func TestProxyCacheExpiration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyCacheStaleWhileRevalidate(t *testing.T) {
|
func TestProxyCacheStaleWhileRevalidate(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 200*time.Millisecond)
|
pc := NewProxyCache(nil, false, 200*time.Millisecond, 0, 0)
|
||||||
|
|
||||||
key := "stale-test"
|
key := "stale-test"
|
||||||
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
@ -356,7 +356,7 @@ func TestProxyCacheStaleWhileRevalidate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyCacheLock(t *testing.T) {
|
func TestProxyCacheLock(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, true, 0)
|
pc := NewProxyCache(nil, true, 0, 0, 0)
|
||||||
|
|
||||||
key := "lock-test"
|
key := "lock-test"
|
||||||
|
|
||||||
@ -388,7 +388,7 @@ func TestProxyCacheMatchRule(t *testing.T) {
|
|||||||
{Path: "/static/*", Methods: []string{"GET"}, MaxAge: 1 * time.Hour},
|
{Path: "/static/*", Methods: []string{"GET"}, MaxAge: 1 * time.Hour},
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := NewProxyCache(rules, false, 0)
|
pc := NewProxyCache(rules, false, 0, 0, 0)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
path string
|
path string
|
||||||
@ -416,7 +416,7 @@ func TestProxyCacheMatchRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyCacheDelete(t *testing.T) {
|
func TestProxyCacheDelete(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
|
|
||||||
key := "key1"
|
key := "key1"
|
||||||
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 10*time.Minute)
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 10*time.Minute)
|
||||||
@ -429,7 +429,7 @@ func TestProxyCacheDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyCacheClear(t *testing.T) {
|
func TestProxyCacheClear(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
|
|
||||||
pc.Set(hashKey("a"), "a", []byte("a"), nil, 200, 10*time.Minute)
|
pc.Set(hashKey("a"), "a", []byte("a"), nil, 200, 10*time.Minute)
|
||||||
pc.Set(hashKey("b"), "b", []byte("b"), nil, 200, 10*time.Minute)
|
pc.Set(hashKey("b"), "b", []byte("b"), nil, 200, 10*time.Minute)
|
||||||
@ -470,3 +470,93 @@ func TestPathMatch(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxyCacheGetStaleIfError(t *testing.T) {
|
||||||
|
pc := NewProxyCache(nil, false, 0, 200*time.Millisecond, 0)
|
||||||
|
|
||||||
|
key := "stale-error-test"
|
||||||
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待过期但仍在 stale_if_error 窗口内
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
|
||||||
|
// isTimeout=false,应该使用 staleIfError 窗口
|
||||||
|
entry, ok := pc.GetStale(hashKey(key), key, false)
|
||||||
|
if !ok {
|
||||||
|
t.Error("stale entry should be usable on error")
|
||||||
|
}
|
||||||
|
if entry == nil {
|
||||||
|
t.Error("expected stale entry data")
|
||||||
|
}
|
||||||
|
if string(entry.Data) != "data" {
|
||||||
|
t.Errorf("entry.Data = %q, want %q", entry.Data, "data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTimeout=true,staleIfTimeout=0,不应该可用
|
||||||
|
if _, ok2 := pc.GetStale(hashKey(key), key, true); ok2 {
|
||||||
|
t.Error("stale entry should NOT be usable on timeout when staleIfTimeout=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyCacheGetStaleIfTimeout(t *testing.T) {
|
||||||
|
pc := NewProxyCache(nil, false, 0, 0, 300*time.Millisecond)
|
||||||
|
|
||||||
|
key := "stale-timeout-test"
|
||||||
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待过期但仍在 stale_if_timeout 窗口内
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
|
||||||
|
// isTimeout=true,应该使用 staleIfTimeout 窗口
|
||||||
|
entry, ok := pc.GetStale(hashKey(key), key, true)
|
||||||
|
if !ok {
|
||||||
|
t.Error("stale entry should be usable on timeout")
|
||||||
|
}
|
||||||
|
if entry == nil {
|
||||||
|
t.Error("expected stale entry data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTimeout=false,staleIfError=0,不应该可用
|
||||||
|
if _, ok2 := pc.GetStale(hashKey(key), key, false); ok2 {
|
||||||
|
t.Error("stale entry should NOT be usable on error when staleIfError=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyCacheGetStaleExpired(t *testing.T) {
|
||||||
|
pc := NewProxyCache(nil, false, 0, 100*time.Millisecond, 100*time.Millisecond)
|
||||||
|
|
||||||
|
key := "stale-expired-test"
|
||||||
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 50*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待超过 stale 窗口
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
// 两种情况都不应该可用
|
||||||
|
if _, ok := pc.GetStale(hashKey(key), key, false); ok {
|
||||||
|
t.Error("stale entry should NOT be usable after stale window expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok2 := pc.GetStale(hashKey(key), key, true); ok2 {
|
||||||
|
t.Error("stale entry should NOT be usable on timeout after stale window expired")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyCacheGetStaleNotExpired(t *testing.T) {
|
||||||
|
pc := NewProxyCache(nil, false, 0, 100*time.Millisecond, 100*time.Millisecond)
|
||||||
|
|
||||||
|
key := "stale-fresh-test"
|
||||||
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 200*time.Millisecond)
|
||||||
|
|
||||||
|
// 未过期,两种情况都应该可用(返回新鲜数据)
|
||||||
|
entry, ok := pc.GetStale(hashKey(key), key, false)
|
||||||
|
if !ok {
|
||||||
|
t.Error("fresh entry should be usable")
|
||||||
|
}
|
||||||
|
if string(entry.Data) != "data" {
|
||||||
|
t.Errorf("entry.Data = %q, want %q", entry.Data, "data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok2 := pc.GetStale(hashKey(key), key, true); !ok2 {
|
||||||
|
t.Error("fresh entry should be usable on timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
111
internal/cache/disk_cache_test.go
vendored
111
internal/cache/disk_cache_test.go
vendored
@ -384,3 +384,114 @@ func TestDiskCacheRestart(t *testing.T) {
|
|||||||
t.Errorf("Data = %q, want %q", entry.Data, data)
|
t.Errorf("Data = %q, want %q", entry.Data, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiskCacheGetStaleIfError(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
cfg := &DiskCacheConfig{
|
||||||
|
Path: tmpDir,
|
||||||
|
Levels: "1:2",
|
||||||
|
StaleIfError: 200 * time.Millisecond,
|
||||||
|
StaleIfTimeout: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
dc, err := NewDiskCache(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewDiskCache failed: %v", err)
|
||||||
|
}
|
||||||
|
defer dc.Stop()
|
||||||
|
<-dc.loadCh
|
||||||
|
|
||||||
|
hashKey := uint64(12345)
|
||||||
|
origKey := "GET:/api/test"
|
||||||
|
dc.Set(hashKey, origKey, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待过期但仍在 stale_if_error 窗口内
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
|
||||||
|
// isTimeout=false,应该使用 staleIfError 窗口
|
||||||
|
entry, ok := dc.GetStale(hashKey, origKey, false)
|
||||||
|
if !ok {
|
||||||
|
t.Error("stale entry should be usable on error")
|
||||||
|
}
|
||||||
|
if entry == nil || string(entry.Data) != "data" {
|
||||||
|
t.Errorf("entry.Data = %v, want %q", entry, "data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTimeout=true,staleIfTimeout=0,不应该可用
|
||||||
|
if _, ok2 := dc.GetStale(hashKey, origKey, true); ok2 {
|
||||||
|
t.Error("stale entry should NOT be usable on timeout when staleIfTimeout=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiskCacheGetStaleIfTimeout(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
cfg := &DiskCacheConfig{
|
||||||
|
Path: tmpDir,
|
||||||
|
Levels: "1:2",
|
||||||
|
StaleIfError: 0,
|
||||||
|
StaleIfTimeout: 300 * time.Millisecond,
|
||||||
|
}
|
||||||
|
|
||||||
|
dc, err := NewDiskCache(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewDiskCache failed: %v", err)
|
||||||
|
}
|
||||||
|
defer dc.Stop()
|
||||||
|
<-dc.loadCh
|
||||||
|
|
||||||
|
hashKey := uint64(12345)
|
||||||
|
origKey := "GET:/api/test"
|
||||||
|
dc.Set(hashKey, origKey, []byte("data"), nil, 200, 100*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待过期但仍在 stale_if_timeout 窗口内
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
|
||||||
|
// isTimeout=true,应该使用 staleIfTimeout 窗口
|
||||||
|
entry, ok := dc.GetStale(hashKey, origKey, true)
|
||||||
|
if !ok {
|
||||||
|
t.Error("stale entry should be usable on timeout")
|
||||||
|
}
|
||||||
|
if entry == nil {
|
||||||
|
t.Error("expected stale entry data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTimeout=false,staleIfError=0,不应该可用
|
||||||
|
if _, ok2 := dc.GetStale(hashKey, origKey, false); ok2 {
|
||||||
|
t.Error("stale entry should NOT be usable on error when staleIfError=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiskCacheGetStaleExpired(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
cfg := &DiskCacheConfig{
|
||||||
|
Path: tmpDir,
|
||||||
|
Levels: "1:2",
|
||||||
|
StaleIfError: 100 * time.Millisecond,
|
||||||
|
StaleIfTimeout: 100 * time.Millisecond,
|
||||||
|
}
|
||||||
|
|
||||||
|
dc, err := NewDiskCache(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewDiskCache failed: %v", err)
|
||||||
|
}
|
||||||
|
defer dc.Stop()
|
||||||
|
<-dc.loadCh
|
||||||
|
|
||||||
|
hashKey := uint64(12345)
|
||||||
|
origKey := "GET:/api/test"
|
||||||
|
dc.Set(hashKey, origKey, []byte("data"), nil, 200, 50*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待超过 stale 窗口
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
if _, ok := dc.GetStale(hashKey, origKey, false); ok {
|
||||||
|
t.Error("stale entry should NOT be usable after stale window expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok2 := dc.GetStale(hashKey, origKey, true); ok2 {
|
||||||
|
t.Error("stale entry should NOT be usable on timeout after stale window expired")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
26
internal/cache/purge_test.go
vendored
26
internal/cache/purge_test.go
vendored
@ -109,7 +109,7 @@ func TestNewPurgeAPI(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("with cache", func(t *testing.T) {
|
t.Run("with cache", func(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Path: "/custom/purge",
|
Path: "/custom/purge",
|
||||||
Allow: []string{"127.0.0.1"},
|
Allow: []string{"127.0.0.1"},
|
||||||
@ -276,7 +276,7 @@ func TestPurgeAPI_ServeHTTP_AccessForbidden(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_Unauthorized(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_Unauthorized(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Allow: []string{"127.0.0.1"},
|
Allow: []string{"127.0.0.1"},
|
||||||
Auth: config.CacheAPIAuthConfig{
|
Auth: config.CacheAPIAuthConfig{
|
||||||
@ -303,7 +303,7 @@ func TestPurgeAPI_ServeHTTP_Unauthorized(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_BadRequest(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_BadRequest(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Allow: []string{},
|
Allow: []string{},
|
||||||
}
|
}
|
||||||
@ -338,7 +338,7 @@ func TestPurgeAPI_ServeHTTP_BadRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_PurgeByPath(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_PurgeByPath(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
key := "GET:/api/test"
|
key := "GET:/api/test"
|
||||||
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey(key), key, []byte("data"), nil, 200, 10*60*time.Second)
|
||||||
|
|
||||||
@ -376,7 +376,7 @@ func TestPurgeAPI_ServeHTTP_PurgeByPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_PurgeByPath_NotFound(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_PurgeByPath_NotFound(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Allow: []string{},
|
Allow: []string{},
|
||||||
}
|
}
|
||||||
@ -405,7 +405,7 @@ func TestPurgeAPI_ServeHTTP_PurgeByPath_NotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_PurgeByPattern(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_PurgeByPattern(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
pc.Set(hashKey("GET:/api/users"), "GET:/api/users", []byte("users"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/api/users"), "GET:/api/users", []byte("users"), nil, 200, 10*60*time.Second)
|
||||||
pc.Set(hashKey("GET:/api/posts"), "GET:/api/posts", []byte("posts"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/api/posts"), "GET:/api/posts", []byte("posts"), nil, 200, 10*60*time.Second)
|
||||||
pc.Set(hashKey("GET:/static/css"), "GET:/static/css", []byte("css"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/static/css"), "GET:/static/css", []byte("css"), nil, 200, 10*60*time.Second)
|
||||||
@ -444,7 +444,7 @@ func TestPurgeAPI_ServeHTTP_PurgeByPattern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_PurgeByPattern_Wildcard(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_PurgeByPattern_Wildcard(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
pc.Set(hashKey("GET:/a"), "GET:/a", []byte("a"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/a"), "GET:/a", []byte("a"), nil, 200, 10*60*time.Second)
|
||||||
pc.Set(hashKey("GET:/b"), "GET:/b", []byte("b"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/b"), "GET:/b", []byte("b"), nil, 200, 10*60*time.Second)
|
||||||
|
|
||||||
@ -472,7 +472,7 @@ func TestPurgeAPI_ServeHTTP_PurgeByPattern_Wildcard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_PurgeByPattern_DirPrefix(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_PurgeByPattern_DirPrefix(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
pc.Set(hashKey("GET:/api/v1/users"), "GET:/api/v1/users", []byte("u"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/api/v1/users"), "GET:/api/v1/users", []byte("u"), nil, 200, 10*60*time.Second)
|
||||||
pc.Set(hashKey("GET:/api/v2/posts"), "GET:/api/v2/posts", []byte("p"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/api/v2/posts"), "GET:/api/v2/posts", []byte("p"), nil, 200, 10*60*time.Second)
|
||||||
pc.Set(hashKey("GET:/other"), "GET:/other", []byte("o"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/other"), "GET:/other", []byte("o"), nil, 200, 10*60*time.Second)
|
||||||
@ -501,7 +501,7 @@ func TestPurgeAPI_ServeHTTP_PurgeByPattern_DirPrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_ContentType(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_ContentType(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Allow: []string{},
|
Allow: []string{},
|
||||||
}
|
}
|
||||||
@ -535,7 +535,7 @@ func TestPurgeAPI_ServeHTTP_ContentType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_AccessAllowed(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_AccessAllowed(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Allow: []string{"10.0.0.0/8"},
|
Allow: []string{"10.0.0.0/8"},
|
||||||
}
|
}
|
||||||
@ -558,7 +558,7 @@ func TestPurgeAPI_ServeHTTP_AccessAllowed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_TokenAuth(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_TokenAuth(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Allow: []string{},
|
Allow: []string{},
|
||||||
Auth: config.CacheAPIAuthConfig{
|
Auth: config.CacheAPIAuthConfig{
|
||||||
@ -599,7 +599,7 @@ func TestPurgeAPI_ServeHTTP_TokenAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPurgeAPI_ServeHTTP_AuthTypeNone(t *testing.T) {
|
func TestPurgeAPI_ServeHTTP_AuthTypeNone(t *testing.T) {
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
cfg := &config.CacheAPIConfig{
|
cfg := &config.CacheAPIConfig{
|
||||||
Allow: []string{},
|
Allow: []string{},
|
||||||
Auth: config.CacheAPIAuthConfig{
|
Auth: config.CacheAPIAuthConfig{
|
||||||
@ -711,7 +711,7 @@ func TestPurgeAPI_ErrorResponse(t *testing.T) {
|
|||||||
|
|
||||||
func TestPurgeAPI_PurgeByPath_WrongMethod(t *testing.T) {
|
func TestPurgeAPI_PurgeByPath_WrongMethod(t *testing.T) {
|
||||||
// Test that hashPath only uses GET, so purging a POST-cached entry won't work
|
// Test that hashPath only uses GET, so purging a POST-cached entry won't work
|
||||||
pc := NewProxyCache(nil, false, 0)
|
pc := NewProxyCache(nil, false, 0, 0, 0)
|
||||||
// Set a cache entry with GET:/api/test key
|
// Set a cache entry with GET:/api/test key
|
||||||
pc.Set(hashKey("GET:/api/test"), "GET:/api/test", []byte("data"), nil, 200, 10*60*time.Second)
|
pc.Set(hashKey("GET:/api/test"), "GET:/api/test", []byte("data"), nil, 200, 10*60*time.Second)
|
||||||
|
|
||||||
|
|||||||
72
internal/cache/tiered_cache_test.go
vendored
72
internal/cache/tiered_cache_test.go
vendored
@ -357,3 +357,75 @@ func TestTieredCacheCacheStats(t *testing.T) {
|
|||||||
t.Errorf("Entries = %d, should be >= 1", stats.Entries)
|
t.Errorf("Entries = %d, should be >= 1", stats.Entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTieredCacheGetStaleL1Hit(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
cfg := &TieredCacheConfig{
|
||||||
|
L1MaxEntries: 100,
|
||||||
|
L1MaxSize: 1024 * 1024,
|
||||||
|
L2Config: &DiskCacheConfig{
|
||||||
|
Path: tmpDir,
|
||||||
|
Levels: "1:2",
|
||||||
|
},
|
||||||
|
StaleIfError: 200 * time.Millisecond,
|
||||||
|
StaleIfTimeout: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
tc, err := NewTieredCache(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewTieredCache failed: %v", err)
|
||||||
|
}
|
||||||
|
defer tc.Stop()
|
||||||
|
|
||||||
|
hashKey := uint64(54321)
|
||||||
|
origKey := "GET:/api/tiered"
|
||||||
|
tc.Set(hashKey, origKey, []byte("l1data"), nil, 200, 100*time.Millisecond)
|
||||||
|
|
||||||
|
// 等待过期但仍在 stale_if_error 窗口内
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
|
||||||
|
// isTimeout=false,应该从 L1 获取 stale 缓存
|
||||||
|
entry, ok := tc.GetStale(hashKey, origKey, false)
|
||||||
|
if !ok {
|
||||||
|
t.Error("stale entry should be usable on error from L1")
|
||||||
|
}
|
||||||
|
if entry == nil || string(entry.Data) != "l1data" {
|
||||||
|
t.Errorf("entry.Data = %v, want %q", entry, "l1data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTimeout=true,staleIfTimeout=0,不应该可用
|
||||||
|
if _, ok2 := tc.GetStale(hashKey, origKey, true); ok2 {
|
||||||
|
t.Error("stale entry should NOT be usable on timeout when staleIfTimeout=0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTieredCacheGetStaleMiss(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
cfg := &TieredCacheConfig{
|
||||||
|
L1MaxEntries: 100,
|
||||||
|
L1MaxSize: 1024 * 1024,
|
||||||
|
L2Config: &DiskCacheConfig{
|
||||||
|
Path: tmpDir,
|
||||||
|
Levels: "1:2",
|
||||||
|
},
|
||||||
|
StaleIfError: 200 * time.Millisecond,
|
||||||
|
StaleIfTimeout: 200 * time.Millisecond,
|
||||||
|
}
|
||||||
|
|
||||||
|
tc, err := NewTieredCache(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewTieredCache failed: %v", err)
|
||||||
|
}
|
||||||
|
defer tc.Stop()
|
||||||
|
|
||||||
|
// 不存在的 key
|
||||||
|
if _, ok := tc.GetStale(99999, "nonexistent", false); ok {
|
||||||
|
t.Error("should not find nonexistent key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok2 := tc.GetStale(99999, "nonexistent", true); ok2 {
|
||||||
|
t.Error("should not find nonexistent key on timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user