first commit
Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped

This commit is contained in:
Rogee
2025-09-28 10:05:07 +08:00
commit 7fcabe0225
481 changed files with 125127 additions and 0 deletions

View File

@@ -0,0 +1,405 @@
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)
})
})
}