Files
yoyo/internal/cache/cache_test.go
z.to b71f76c8b3 feat: 添加本地缓存功能,减少API调用
- 实现SQLite缓存模块,支持高效查询和存储
- 添加缓存键生成策略(基于原文+语言对的SHA256哈希)
- 集成缓存到Translator类,先查缓存再调用API
- 添加缓存管理命令:cache clear, cache stats, cache cleanup
- 实现组合缓存清理策略(数量限制+时间过期)
- 添加完整的单元测试
- 更新配置文件模板,添加缓存配置
- 更新文档和版本记录

版本: v0.5.1
2026-03-29 21:10:28 +08:00

326 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cache
import (
"context"
"os"
"path/filepath"
"testing"
)
func TestGenerateCacheKey(t *testing.T) {
tests := []struct {
name string
text string
fromLang string
toLang string
wantSame bool
}{
{
name: "相同输入应生成相同键",
text: "Hello world",
fromLang: "en",
toLang: "zh-CN",
wantSame: true,
},
{
name: "不同文本应生成不同键",
text: "Hello universe",
fromLang: "en",
toLang: "zh-CN",
wantSame: false,
},
{
name: "不同语言对应生成不同键",
text: "Hello world",
fromLang: "en",
toLang: "zh-TW",
wantSame: false,
},
{
name: "大小写不敏感的语言代码",
text: "Hello world",
fromLang: "EN",
toLang: "zh-cn",
wantSame: true,
},
{
name: "多余空白字符应规范化",
text: " Hello world ",
fromLang: "en",
toLang: "zh-CN",
wantSame: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key1 := GenerateCacheKey(tt.text, tt.fromLang, tt.toLang)
key2 := GenerateCacheKey(tt.text, tt.fromLang, tt.toLang)
if key1 != key2 {
t.Errorf("相同输入生成了不同的键: %s != %s", key1, key2)
}
// 检查键的长度SHA256哈希应为64个字符
if len(key1) != 64 {
t.Errorf("缓存键长度不正确: got %d, want 64", len(key1))
}
})
}
}
func TestSQLiteCache(t *testing.T) {
// 创建临时目录
tmpDir, err := os.MkdirTemp("", "cache_test")
if err != nil {
t.Fatalf("创建临时目录失败: %v", err)
}
defer os.RemoveAll(tmpDir)
dbPath := filepath.Join(tmpDir, "test_cache.db")
// 创建缓存配置
config := &CacheConfig{
Enabled: true,
MaxRecords: 100,
ExpireDays: 1,
DBPath: dbPath,
}
// 创建缓存实例
cache, err := NewSQLiteCache(config)
if err != nil {
t.Fatalf("创建缓存实例失败: %v", err)
}
defer cache.Close()
ctx := context.Background()
// 测试设置缓存
entry := &CacheEntry{
CacheKey: "test_key_1",
OriginalText: "Hello world",
TranslatedText: "你好世界",
FromLang: "en",
ToLang: "zh-CN",
Model: "gpt-3.5-turbo",
PromptName: "simple",
PromptContent: "请用简单易懂的语言翻译以下内容。",
PromptTokens: 10,
CompletionTokens: 5,
TotalTokens: 15,
}
err = cache.Set(ctx, entry)
if err != nil {
t.Fatalf("设置缓存失败: %v", err)
}
// 测试获取缓存
cachedEntry, err := cache.Get(ctx, "test_key_1")
if err != nil {
t.Fatalf("获取缓存失败: %v", err)
}
if cachedEntry == nil {
t.Fatal("缓存未命中")
}
if cachedEntry.TranslatedText != "你好世界" {
t.Errorf("缓存翻译结果不正确: got %s, want 你好世界", cachedEntry.TranslatedText)
}
// 测试缓存未命中
missingEntry, err := cache.Get(ctx, "non_existent_key")
if err != nil {
t.Fatalf("查询不存在的缓存失败: %v", err)
}
if missingEntry != nil {
t.Error("不存在的缓存应该返回nil")
}
// 测试统计信息
stats, err := cache.Stats(ctx)
if err != nil {
t.Fatalf("获取统计信息失败: %v", err)
}
if stats.TotalRecords != 1 {
t.Errorf("统计记录数不正确: got %d, want 1", stats.TotalRecords)
}
// 测试删除缓存
err = cache.Delete(ctx, "test_key_1")
if err != nil {
t.Fatalf("删除缓存失败: %v", err)
}
// 验证删除
deletedEntry, err := cache.Get(ctx, "test_key_1")
if err != nil {
t.Fatalf("查询已删除的缓存失败: %v", err)
}
if deletedEntry != nil {
t.Error("已删除的缓存应该返回nil")
}
// 测试清空缓存
err = cache.Set(ctx, entry)
if err != nil {
t.Fatalf("设置缓存失败: %v", err)
}
err = cache.Clear(ctx)
if err != nil {
t.Fatalf("清空缓存失败: %v", err)
}
stats, err = cache.Stats(ctx)
if err != nil {
t.Fatalf("获取统计信息失败: %v", err)
}
if stats.TotalRecords != 0 {
t.Errorf("清空后记录数不正确: got %d, want 0", stats.TotalRecords)
}
}
func TestCacheExpiration(t *testing.T) {
// 创建临时目录
tmpDir, err := os.MkdirTemp("", "cache_test")
if err != nil {
t.Fatalf("创建临时目录失败: %v", err)
}
defer os.RemoveAll(tmpDir)
dbPath := filepath.Join(tmpDir, "test_cache.db")
// 创建缓存配置,设置很短的过期时间
config := &CacheConfig{
Enabled: true,
MaxRecords: 100,
ExpireDays: 0, // 0天表示立即过期
DBPath: dbPath,
}
// 创建缓存实例
cache, err := NewSQLiteCache(config)
if err != nil {
t.Fatalf("创建缓存实例失败: %v", err)
}
defer cache.Close()
ctx := context.Background()
// 设置缓存
entry := &CacheEntry{
CacheKey: "test_key_1",
OriginalText: "Hello world",
TranslatedText: "你好世界",
FromLang: "en",
ToLang: "zh-CN",
Model: "gpt-3.5-turbo",
PromptTokens: 10,
CompletionTokens: 5,
TotalTokens: 15,
}
err = cache.Set(ctx, entry)
if err != nil {
t.Fatalf("设置缓存失败: %v", err)
}
// 立即清理应该删除所有记录因为过期时间为0
err = cache.Cleanup(ctx)
if err != nil {
t.Fatalf("清理缓存失败: %v", err)
}
// 检查统计信息
stats, err := cache.Stats(ctx)
if err != nil {
t.Fatalf("获取统计信息失败: %v", err)
}
if stats.TotalRecords != 0 {
t.Errorf("清理后记录数不正确: got %d, want 0", stats.TotalRecords)
}
}
func TestNormalizeText(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "正常文本",
input: "Hello world",
expected: "Hello world",
},
{
name: "多余空白字符",
input: " Hello world ",
expected: "Hello world",
},
{
name: "制表符和换行符",
input: "Hello\tworld\n",
expected: "Hello world",
},
{
name: "空字符串",
input: "",
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := normalizeText(tt.input)
if result != tt.expected {
t.Errorf("normalizeText(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestNormalizeLanguageCode(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "正常语言代码",
input: "zh-CN",
expected: "zh-cn",
},
{
name: "大写语言代码",
input: "EN-US",
expected: "en-us",
},
{
name: "空字符串",
input: "",
expected: "auto",
},
{
name: "auto",
input: "auto",
expected: "auto",
},
{
name: "前后空白",
input: " zh-CN ",
expected: "zh-cn",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := normalizeLanguageCode(tt.input)
if result != tt.expected {
t.Errorf("normalizeLanguageCode(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}