package cache_test import ( "testing" "time" . "github.com/smartystreets/goconvey/convey" "github.com/subconverter-go/internal/cache" "github.com/subconverter-go/internal/conversion" "github.com/subconverter-go/internal/logging" ) func TestCacheManager(t *testing.T) { Convey("CacheManager", t, func() { // 创建日志记录器 logger, err := logging.NewLogger(&logging.LoggingConfig{ Level: "info", Format: "text", Output: "stdout", }) So(err, ShouldBeNil) // 创建缓存配置 cacheConfig := &cache.CacheConfig{ Enabled: true, TTL: 1 * time.Second, MaxSize: 10, Cleanup: 100 * time.Millisecond, } // 创建缓存管理器 cacheMgr := cache.NewCacheManager(cacheConfig, logger) defer cacheMgr.Close() Convey("应该能创建和初始化缓存管理器", func() { So(cacheMgr, ShouldNotBeNil) So(cacheMgr.IsEnabled(), ShouldBeTrue) So(cacheMgr.GetSize(), ShouldEqual, 0) So(cacheMgr.GetTTL(), ShouldEqual, 1*time.Second) So(cacheMgr.GetMaxSize(), ShouldEqual, 10) }) Convey("应该能生成缓存键", func() { request := &conversion.ConversionRequest{ Target: "clash", URL: "https://example.com/subscribe", Group: "Test", UDP: true, Emoji: false, } key1 := cacheMgr.GenerateKey(request) So(key1, ShouldNotBeEmpty) So(len(key1), ShouldEqual, 32) // MD5 hash length // 相同请求应该生成相同的键 key2 := cacheMgr.GenerateKey(request) So(key2, ShouldEqual, key1) // 不同请求应该生成不同的键 request.Group = "Different" key3 := cacheMgr.GenerateKey(request) So(key3, ShouldNotEqual, key1) }) Convey("应该能设置和获取缓存条目", func() { request := &conversion.ConversionRequest{ Target: "clash", URL: "https://example.com/subscribe", } key := cacheMgr.GenerateKey(request) response := &conversion.ConversionResponse{ Success: true, Content: "test content", } // 设置缓存 err := cacheMgr.Set(key, response) So(err, ShouldBeNil) So(cacheMgr.GetSize(), ShouldEqual, 1) // 获取缓存 cachedResponse, exists := cacheMgr.Get(key) So(exists, ShouldBeTrue) So(cachedResponse, ShouldNotBeNil) So(cachedResponse.Success, ShouldEqual, true) So(cachedResponse.Content, ShouldEqual, "test content") }) Convey("应该能处理缓存未命中", func() { key := "nonexistent-key" response, exists := cacheMgr.Get(key) So(exists, ShouldBeFalse) So(response, ShouldBeNil) // 检查统计信息 stats := cacheMgr.GetStats() So(stats.Misses, ShouldBeGreaterThan, 0) }) Convey("应该能处理缓存过期", func() { // 使用很短的TTL shortTTLConfig := &cache.CacheConfig{ Enabled: true, TTL: 100 * time.Millisecond, MaxSize: 10, Cleanup: 50 * time.Millisecond, } shortCache := cache.NewCacheManager(shortTTLConfig, logger) defer shortCache.Close() request := &conversion.ConversionRequest{ Target: "clash", URL: "https://example.com/subscribe", } key := shortCache.GenerateKey(request) response := &conversion.ConversionResponse{ Success: true, Content: "test content", } // 设置缓存 err := shortCache.Set(key, response) So(err, ShouldBeNil) // 立即获取应该成功 cachedResponse, exists := shortCache.Get(key) So(exists, ShouldBeTrue) So(cachedResponse, ShouldNotBeNil) // 等待过期 time.Sleep(150 * time.Millisecond) // 再次获取应该失败 cachedResponse, exists = shortCache.Get(key) So(exists, ShouldBeFalse) So(cachedResponse, ShouldBeNil) }) Convey("应该能执行LRU淘汰策略", func() { // 创建小容量缓存 lruConfig := &cache.CacheConfig{ Enabled: true, TTL: 10 * time.Second, MaxSize: 2, Cleanup: 1 * time.Second, } lruCache := cache.NewCacheManager(lruConfig, logger) defer lruCache.Close() // 添加第一个条目 request1 := &conversion.ConversionRequest{Target: "clash", URL: "https://example1.com"} key1 := lruCache.GenerateKey(request1) response1 := &conversion.ConversionResponse{Success: true, Content: "content1"} lruCache.Set(key1, response1) // 添加第二个条目 request2 := &conversion.ConversionRequest{Target: "surge", URL: "https://example2.com"} key2 := lruCache.GenerateKey(request2) response2 := &conversion.ConversionResponse{Success: true, Content: "content2"} lruCache.Set(key2, response2) So(lruCache.GetSize(), ShouldEqual, 2) // 访问第一个条目以更新其LRU状态 lruCache.Get(key1) // 添加第三个条目,应该淘汰第二个条目 request3 := &conversion.ConversionRequest{Target: "v2ray", URL: "https://example3.com"} key3 := lruCache.GenerateKey(request3) response3 := &conversion.ConversionResponse{Success: true, Content: "content3"} lruCache.Set(key3, response3) // 缓存大小应该仍然是2 So(lruCache.GetSize(), ShouldEqual, 2) // 第一个条目应该还存在(被访问过) _, exists1 := lruCache.Get(key1) So(exists1, ShouldBeTrue) // 第二个条目应该被淘汰 _, exists2 := lruCache.Get(key2) So(exists2, ShouldBeFalse) // 第三个条目应该存在 _, exists3 := lruCache.Get(key3) So(exists3, ShouldBeTrue) }) Convey("应该能获取统计信息", func() { request := &conversion.ConversionRequest{ Target: "clash", URL: "https://example.com/subscribe", } key := cacheMgr.GenerateKey(request) response := &conversion.ConversionResponse{ Success: true, Content: "test content", } // 初始统计 stats := cacheMgr.GetStats() So(stats.Hits, ShouldEqual, 0) So(stats.Misses, ShouldEqual, 0) So(stats.HitRate, ShouldEqual, 0.0) // 设置缓存 cacheMgr.Set(key, response) // 命中缓存 cacheMgr.Get(key) // 未命中缓存 cacheMgr.Get("nonexistent") // 检查统计信息 stats = cacheMgr.GetStats() So(stats.Hits, ShouldEqual, 1) So(stats.Misses, ShouldEqual, 1) So(stats.HitRate, ShouldEqual, 0.5) }) Convey("应该能移除和清空缓存", func() { request := &conversion.ConversionRequest{ Target: "clash", URL: "https://example.com/subscribe", } key := cacheMgr.GenerateKey(request) response := &conversion.ConversionResponse{ Success: true, Content: "test content", } // 设置缓存 cacheMgr.Set(key, response) So(cacheMgr.GetSize(), ShouldEqual, 1) // 移除特定条目 cacheMgr.Remove(key) So(cacheMgr.GetSize(), ShouldEqual, 0) // 再次获取应该失败 _, exists := cacheMgr.Get(key) So(exists, ShouldBeFalse) // 重新添加条目 cacheMgr.Set(key, response) So(cacheMgr.GetSize(), ShouldEqual, 1) // 清空所有缓存 cacheMgr.Clear() So(cacheMgr.GetSize(), ShouldEqual, 0) }) Convey("应该能处理禁用的缓存", func() { disabledConfig := &cache.CacheConfig{ Enabled: false, } disabledCache := cache.NewCacheManager(disabledConfig, logger) defer disabledCache.Close() So(disabledCache.IsEnabled(), ShouldBeFalse) request := &conversion.ConversionRequest{ Target: "clash", URL: "https://example.com/subscribe", } key := disabledCache.GenerateKey(request) response := &conversion.ConversionResponse{ Success: true, Content: "test content", } // 设置缓存应该静默失败 err := disabledCache.Set(key, response) So(err, ShouldBeNil) // 获取缓存应该失败 _, exists := disabledCache.Get(key) So(exists, ShouldBeFalse) // 统计信息应该为空 stats := disabledCache.GetStats() So(stats.Hits, ShouldEqual, 0) So(stats.Misses, ShouldEqual, 0) }) Convey("应该能获取缓存条目详细信息", func() { request := &conversion.ConversionRequest{ Target: "clash", URL: "https://example.com/subscribe", } key := cacheMgr.GenerateKey(request) response := &conversion.ConversionResponse{ Success: true, Content: "test content", } // 设置缓存 cacheMgr.Set(key, response) // 获取条目信息 entry, exists := cacheMgr.GetEntry(key) So(exists, ShouldBeTrue) So(entry, ShouldNotBeNil) So(entry.Response.Success, ShouldEqual, true) So(entry.AccessCount, ShouldEqual, 1) // 访问一次以增加计数 cacheMgr.Get(key) // 再次获取条目信息 entry, exists = cacheMgr.GetEntry(key) So(exists, ShouldBeTrue) So(entry.AccessCount, ShouldEqual, 2) }) Convey("应该能获取所有缓存键", func() { // 清空缓存 cacheMgr.Clear() requests := []*conversion.ConversionRequest{ {Target: "clash", URL: "https://example1.com"}, {Target: "surge", URL: "https://example2.com"}, {Target: "v2ray", URL: "https://example3.com"}, } // 添加多个条目 for _, req := range requests { key := cacheMgr.GenerateKey(req) response := &conversion.ConversionResponse{Success: true, Content: "content"} cacheMgr.Set(key, response) } // 获取所有键 keys := cacheMgr.GetAllKeys() So(len(keys), ShouldEqual, 3) // 验证所有键都存在 for _, req := range requests { expectedKey := cacheMgr.GenerateKey(req) found := false for _, key := range keys { if key == expectedKey { found = true break } } So(found, ShouldBeTrue) } }) }) } func TestCacheManagerConfiguration(t *testing.T) { Convey("CacheManager Configuration", t, func() { logger, err := logging.NewLogger(&logging.LoggingConfig{ Level: "info", Format: "text", Output: "stdout", }) So(err, ShouldBeNil) Convey("应该能使用默认配置", func() { cacheMgr := cache.NewCacheManager(nil, logger) defer cacheMgr.Close() So(cacheMgr, ShouldNotBeNil) So(cacheMgr.IsEnabled(), ShouldBeTrue) So(cacheMgr.GetTTL(), ShouldEqual, 5*time.Minute) So(cacheMgr.GetMaxSize(), ShouldEqual, 1000) }) Convey("应该能动态配置缓存参数", func() { config := &cache.CacheConfig{ Enabled: true, TTL: 2 * time.Minute, MaxSize: 500, } cacheMgr := cache.NewCacheManager(config, logger) defer cacheMgr.Close() // 修改TTL newTTL := 10 * time.Minute cacheMgr.SetTTL(newTTL) So(cacheMgr.GetTTL(), ShouldEqual, newTTL) // 修改最大大小 newMaxSize := 2000 cacheMgr.SetMaxSize(newMaxSize) So(cacheMgr.GetMaxSize(), ShouldEqual, newMaxSize) }) }) }