package middleware import ( "context" "encoding/json" "sync" "testing" "time" ) // ==================== P0-03 缓存吊销传播测试 ==================== // 验证:缓存TTL=30s与吊销传播<=5s的矛盾修复 // 修复方案:主动失效机制 + 短TTL兜底 // TestP003_CacheRevocationWithin5Seconds 验证P0-03:吊销传播延迟 <= 5s func TestP003_CacheRevocationWithin5Seconds(t *testing.T) { cache := NewTokenCache() ctx := context.Background() // 1. 设置token状态为active,TTL 30秒 tokenID := "tok_test_123" cache.Set(tokenID, "active", 30*time.Second) // 2. 验证token在缓存中 status, found := cache.Get(tokenID) if !found || status != "active" { t.Fatalf("token should be active in cache before revocation") } // 3. 模拟吊销操作并触发主动失效 revokeTime := time.Now() // 创建事件发布器(模拟) publisher := &mockRevocationPublisher{ subscribers: make([]chan *TokenRevokedEvent, 0), } publisher.Subscribe(ctx) // 发布吊销事件 revokeEvent := &TokenRevokedEvent{ TokenID: tokenID, RevokedAt: revokeTime, Reason: "user_requested", } // 模拟订阅者接收并处理 subscriber := newMockSubscriber(cache) subscriber.Handle(ctx, revokeEvent) // 4. 验证:吊销传播延迟 <= 5s propagationDelay := time.Since(revokeTime) if propagationDelay > 5*time.Second { t.Errorf("P0-03 VIOLATION: revocation propagation delay %v exceeds 5s threshold", propagationDelay) } // 5. 验证:token已从缓存中失效 _, found = cache.Get(tokenID) if found { t.Errorf("P0-03 VIOLATION: token should be invalidated immediately after revocation") } } // TestP003_ActiveInvalidationOverridesTTL 验证主动失效优先级高于TTL func TestP003_ActiveInvalidationOverridesTTL(t *testing.T) { cache := NewTokenCache() // 1. 设置token为active,TTL为30秒(长TTL) tokenID := "tok_long_ttl_123" cache.Set(tokenID, "active", 30*time.Second) // 2. 在TTL过期前主动失效 cache.Invalidate(tokenID) // 3. 验证:token已不存在(主动失效优先) _, found := cache.Get(tokenID) if found { t.Errorf("P0-03 VIOLATION: active invalidation should take precedence over TTL") } } // TestP003_MultipleTokensRevocation 验证批量吊销传播 func TestP003_MultipleTokensRevocation(t *testing.T) { cache := NewTokenCache() ctx := context.Background() // 1. 批量设置100个token tokenCount := 100 tokenIDs := make([]string, tokenCount) for i := 0; i < tokenCount; i++ { tokenIDs[i] = "tok_batch_" + string(rune(i)) cache.Set(tokenIDs[i], "active", 30*time.Second) } // 2. 模拟批量吊销事件 subscriber := newMockSubscriber(cache) startTime := time.Now() for _, tokenID := range tokenIDs { revokeEvent := &TokenRevokedEvent{ TokenID: tokenID, RevokedAt: startTime, Reason: "admin_batch_revoke", } subscriber.Handle(ctx, revokeEvent) } // 3. 验证:所有token都已失效 for _, tokenID := range tokenIDs { _, found := cache.Get(tokenID) if found { t.Errorf("token %s should be invalidated", tokenID) } } // 4. 验证:总传播时间 <= 5s totalPropagation := time.Since(startTime) if totalPropagation > 5*time.Second { t.Errorf("P0-03 VIOLATION: batch revocation took %v, exceeds 5s threshold", totalPropagation) } } // TestP003_RedisPubSubIntegration 验证Redis Pub/Sub集成 func TestP003_RedisPubSubIntegration(t *testing.T) { // 这个测试需要Redis连接,标记为集成测试 // 在CI环境中跳过 t.Skip("Integration test - requires Redis connection") } // TestP003_TTLShortenedTo10Seconds 验证TTL缩短到10秒作为兜底 func TestP003_TTLShortenedTo10Seconds(t *testing.T) { cache := NewTokenCache() // 根据修复设计:TTL从30s缩短到10s作为兜底 expectedMaxTTL := 10 * time.Second tokenID := "tok_ttl_test" cache.Set(tokenID, "active", expectedMaxTTL) // 验证TTL设置正确(通过检查expires时间) cache.mu.RLock() entry, found := cache.data[tokenID] cache.mu.RUnlock() if !found { t.Fatalf("token should be set in cache") } ttl := entry.expires.Sub(time.Now()) if ttl > expectedMaxTTL { t.Errorf("TTL should not exceed %v, got %v", expectedMaxTTL, ttl) } } // TestP003_SubscriberHandlesConcurrentRequests 验证订阅者处理并发请求 func TestP003_SubscriberHandlesConcurrentRequests(t *testing.T) { cache := NewTokenCache() ctx := context.Background() subscriber := newMockSubscriber(cache) tokenID := "tok_concurrent_test" cache.Set(tokenID, "active", 30*time.Second) var wg sync.WaitGroup concurrency := 10 for i := 0; i < concurrency; i++ { wg.Add(1) go func() { defer wg.Done() revokeEvent := &TokenRevokedEvent{ TokenID: tokenID, RevokedAt: time.Now(), Reason: "concurrent_revoke", } subscriber.Handle(ctx, revokeEvent) }() } wg.Wait() // 验证token已失效 _, found := cache.Get(tokenID) if found { t.Errorf("token should be invalidated after concurrent revocation") } } // ==================== Mock实现 ==================== // TokenRevokedEvent 吊销事件 type TokenRevokedEvent struct { TokenID string `json:"token_id"` RevokedAt time.Time `json:"revoked_at"` Reason string `json:"reason"` } // mockRevocationPublisher 模拟发布者 type mockRevocationPublisher struct { subscribers []chan *TokenRevokedEvent mu sync.RWMutex } // Subscribe 订阅 func (p *mockRevocationPublisher) Subscribe(ctx context.Context) { ch := make(chan *TokenRevokedEvent, 100) p.mu.Lock() p.subscribers = append(p.subscribers, ch) p.mu.Unlock() go func() { <-ctx.Done() close(ch) }() } // Publish 发布吊销事件 func (p *mockRevocationPublisher) Publish(event *TokenRevokedEvent) { p.mu.RLock() defer p.mu.RUnlock() for _, ch := range p.subscribers { select { case ch <- event: default: // channel full, skip } } } // mockSubscriber 模拟订阅者 type mockSubscriber struct { cache *TokenCache } // newMockSubscriber 创建订阅者 func newMockSubscriber(cache *TokenCache) *mockSubscriber { return &mockSubscriber{cache: cache} } // Handle 处理吊销事件 func (s *mockSubscriber) Handle(ctx context.Context, event *TokenRevokedEvent) { // 立即失效缓存(主动失效机制) s.cache.Invalidate(event.TokenID) } // ==================== 基准测试 ==================== // BenchmarkP003_RevocationPropagation 基准测试:单token吊销传播 func BenchmarkP003_RevocationPropagation(b *testing.B) { cache := NewTokenCache() subscriber := newMockSubscriber(cache) ctx := context.Background() tokenID := "tok_benchmark" cache.Set(tokenID, "active", 30*time.Second) b.ResetTimer() for i := 0; i < b.N; i++ { // 重新设置 cache.Set(tokenID, "active", 30*time.Second) // 吊销 event := &TokenRevokedEvent{ TokenID: tokenID, RevokedAt: time.Now(), Reason: "benchmark", } subscriber.Handle(ctx, event) } } // BenchmarkP003_BatchRevocation 基准测试:批量吊销 func BenchmarkP003_BatchRevocation(b *testing.B) { cache := NewTokenCache() subscriber := newMockSubscriber(cache) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { // 批量设置100个token for j := 0; j < 100; j++ { tokenID := "tok_batch_" + string(rune(j)) cache.Set(tokenID, "active", 30*time.Second) } // 批量吊销 for j := 0; j < 100; j++ { tokenID := "tok_batch_" + string(rune(j)) event := &TokenRevokedEvent{ TokenID: tokenID, RevokedAt: time.Now(), Reason: "benchmark", } subscriber.Handle(ctx, event) } } } // ==================== 测试报告 ==================== // TestP003_Summary 打印测试总结 func TestP003_Summary(t *testing.T) { t.Log("=== P0-03 缓存吊销传播测试总结 ===") t.Log("设计问题:缓存TTL=30s与吊销传播<=5s矛盾") t.Log("修复方案:主动失效机制 + TTL缩短到10s") t.Log("") t.Log("测试覆盖:") t.Log("1. 单token吊销传播延迟 <= 5s") t.Log("2. 主动失效优先级高于TTL") t.Log("3. 批量吊销传播") t.Log("4. TTL缩短验证") t.Log("5. 并发处理能力") } // SerializeEventForPubSub 序列化事件用于Pub/Sub(辅助函数) func SerializeEventForPubSub(event *TokenRevokedEvent) ([]byte, error) { return json.Marshal(event) } // DeserializeEventFromPubSub 从Pub/Sub反序列化事件 func DeserializeEventFromPubSub(data []byte) (*TokenRevokedEvent, error) { var event TokenRevokedEvent err := json.Unmarshal(data, &event) return &event, err }