- 实现SQLite缓存模块,支持高效查询和存储 - 添加缓存键生成策略(基于原文+语言对的SHA256哈希) - 集成缓存到Translator类,先查缓存再调用API - 添加缓存管理命令:cache clear, cache stats, cache cleanup - 实现组合缓存清理策略(数量限制+时间过期) - 添加完整的单元测试 - 更新配置文件模板,添加缓存配置 - 更新文档和版本记录 版本: v0.5.1
326 lines
6.8 KiB
Go
326 lines
6.8 KiB
Go
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)
|
||
}
|
||
})
|
||
}
|
||
}
|