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
405 lines
10 KiB
Go
405 lines
10 KiB
Go
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)
|
|
})
|
|
})
|
|
} |