Files
fanqie-go/internal/fqapi/cache_manager_test.go
T
chenxin e3d96ca637 ✨ feat(database): 完善请求日志体系并为全表补充时间字段
- request_log 新增 user_agent 列(含旧表 ALTER 迁移)及 ip/status 索引,记录解码后的明文 query(含 key,不脱敏)
- 请求日志批量写入改为多值 INSERT 单次往返,替代事务逐条 Exec
- 新增按本地时区分天聚合查询,状态页展示今日请求量与近 7 天请求趋势(峰值日高亮)
- 新增 TZ 时区支持:.env 配置 TZ 后日志时间戳与状态页全部时间统一生效,二进制内嵌 tzdata 保证容器可用
- 五张表统一补齐 created_at/updated_at 字段,启动时自动补列并按业务时间回填旧行
- device_pool 的 device_header 增加唯一约束;audio_cache 写入改为 UPSERT 修复同 key 重复报错
- 状态页"最近请求"新增 UA 列与限流状态显示;config.yaml 暴露 rate_limit_qps 配置并默认启用请求日志
- deploy.sh 同名版本默认拒绝覆盖,新增 --force/-f 参数显式允许覆盖
2026-10-09 22:12:17 +08:00

103 lines
2.7 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 fqapi
import (
"database/sql"
"fanqie/internal/config"
"fmt"
"sync"
"testing"
_ "github.com/mattn/go-sqlite3"
)
// TestConcurrentCacheAccess 验证删除全局串行锁后并发读写缓存不死锁、不 panic、数据正确(配合 -race)
func TestConcurrentCacheAccess(t *testing.T) {
if err := config.LoadConfig("../../config/config.yaml"); err != nil {
t.Skipf("无法加载配置: %v", err)
}
db, err := sql.Open("sqlite3", t.TempDir()+"/test.db?_journal_mode=WAL&_busy_timeout=5000")
if err != nil {
t.Fatal(err)
}
oldDB, oldDriver := cacheDB, dbDriver
cacheDB, dbDriver = db, "sqlite3"
defer func() {
cacheDB, dbDriver = oldDB, oldDriver
db.Close()
}()
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
value TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
)`); err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
for j := 0; j < 30; j++ {
key := fmt.Sprintf("k-%d-%d", i, j)
if err := UpdateCache(key, "value-"+key, 60); err != nil {
t.Errorf("UpdateCache(%s): %v", key, err)
return
}
if got := GetCache(key); got != "value-"+key {
t.Errorf("GetCache(%s) = %q, want %q", key, got, "value-"+key)
return
}
}
}(i)
}
wg.Wait()
}
// TestWithCache 验证缓存命中不回源、失败响应不缓存
func TestWithCache(t *testing.T) {
if err := config.LoadConfig("../../config/config.yaml"); err != nil {
t.Skipf("无法加载配置: %v", err)
}
db, err := sql.Open("sqlite3", t.TempDir()+"/test.db?_journal_mode=WAL&_busy_timeout=5000")
if err != nil {
t.Fatal(err)
}
oldDB, oldDriver := cacheDB, dbDriver
cacheDB, dbDriver = db, "sqlite3"
defer func() {
cacheDB, dbDriver = oldDB, oldDriver
db.Close()
}()
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY, value TEXT,
created_at INTEGER DEFAULT (strftime('%s', 'now')),
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
)`); err != nil {
t.Fatal(err)
}
calls := 0
fetch := func() (string, error) { calls++; return "result", nil }
if got, _ := withCache("wc_hit", 60, fetch); got != "result" {
t.Fatalf("首次应返回回源结果, got %q", got)
}
if got, _ := withCache("wc_hit", 60, fetch); got != "result" {
t.Fatalf("命中应返回缓存结果, got %q", got)
}
if calls != 1 {
t.Fatalf("命中缓存不应回源, fetch 调用 %d 次", calls)
}
errCalls := 0
errFetch := func() (string, error) { errCalls++; return "", fmt.Errorf("boom") }
withCache("wc_miss", 60, errFetch)
withCache("wc_miss", 60, errFetch)
if errCalls != 2 {
t.Fatalf("失败响应不应被缓存, fetch 调用 %d 次", errCalls)
}
}