You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

120 lines
3.9 KiB

package memory
import (
"testing"
"time"
"yunyan/comm"
"yunyan/pb"
)
// 补偿要找的是「最近一个**已结束**的周期」,口径必须与 cron 的 tick 完全一致:
// 差一周就会去补一个 cron 已经建过的周期(重复生成)或者永远差一格(永远补不上)。
func TestLastFinishedPeriods(t *testing.T) {
loc := time.FixedZone("CST", 8*3600)
cases := []struct {
name string
now time.Time
wantWeek string // 周一~周日
wantMonth string
}{
{
name: "周中",
now: time.Date(2026, 9, 18, 10, 0, 0, 0, loc), // 周五
wantWeek: "2026-09-07~2026-09-13",
wantMonth: "2026-08-01~2026-08-31",
},
{
name: "周一 cron 触发那一刻",
now: time.Date(2026, 9, 14, 0, 30, 0, 0, loc),
wantWeek: "2026-09-07~2026-09-13",
wantMonth: "2026-08-01~2026-08-31",
},
{
name: "周日(本周还没结束,仍看上一周)",
now: time.Date(2026, 9, 20, 23, 59, 0, 0, loc),
wantWeek: "2026-09-07~2026-09-13",
wantMonth: "2026-08-01~2026-08-31",
},
{
name: "跨年:1 月 1 日的上个月是去年 12 月",
now: time.Date(2026, 1, 1, 0, 30, 0, 0, loc),
wantMonth: "2025-12-01~2025-12-31",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if c.wantWeek != "" {
w := lastFinishedWeek(c.now)
if got := w.start + "~" + w.end; got != c.wantWeek {
t.Errorf("上周范围应为 %s,得到 %s", c.wantWeek, got)
}
if w.ptype != comm.MemoryPeriodWeek {
t.Errorf("周期类型应为 week,得到 %s", w.ptype)
}
}
m := lastFinishedMonth(c.now)
if got := m.start + "~" + m.end; got != c.wantMonth {
t.Errorf("上月范围应为 %s,得到 %s", c.wantMonth, got)
}
if m.ptype != comm.MemoryPeriodMonth {
t.Errorf("周期类型应为 month,得到 %s", m.ptype)
}
})
}
}
// 周一 00:30 的 cron 与 ensureRecent 必须算出同一个周期键,
// 否则一个建单、另一个再建一次,同一周会有两份报告在跑。
func TestLastFinishedWeekMatchesCronKey(t *testing.T) {
loc := time.FixedZone("CST", 8*3600)
now := time.Date(2026, 9, 14, 0, 30, 0, 0, loc) // 周一,cron 触发时刻
cronKey := comm.MemoryWeekKey(now.AddDate(0, 0, -1))
if got := lastFinishedWeek(now).key; got != cronKey {
t.Errorf("ensureRecent 算出 %s,cron 算出 %s,两者必须一致", got, cronKey)
}
}
// 重试次数寄存在 error_msg 的 "retry=N;" 前缀里(不加列)。
// 计数丢了的后果是一份永远失败的报告可以无限重试,每次都烧一次 LLM。
func TestRetryCountRoundTrip(t *testing.T) {
cases := []struct {
msg string
want int
}{
{"", 0},
{"统计失败: connection refused", 0},
{"retry=1;统计失败", 1},
{"retry=3;统计失败", 3},
{"retry=;坏格式当没重试过", 0},
{"retry=abc;坏格式当没重试过", 0},
{"retry=2没有分号也当没重试过", 0},
}
for _, c := range cases {
if got := retryCount(c.msg); got != c.want {
t.Errorf("retryCount(%q) = %d,期望 %d", c.msg, got, c.want)
}
}
// 加计数要保住原来的错误正文,换计数不能越叠越长
got := withRetryCount("统计失败: x", 1)
if got != "retry=1;统计失败: x" {
t.Errorf("首次加计数得到 %q", got)
}
if got = withRetryCount(got, 2); got != "retry=2;统计失败: x" {
t.Errorf("再次加计数得到 %q(正文应原样保留、前缀不叠加)", got)
}
}
// fail 覆盖 error_msg 时必须保住已有计数——不然重试上限形同虚设。
func TestFailKeepsRetryCount(t *testing.T) {
rec := &pb.DBMemoryReport{ErrorMsg: "retry=2;上一次的原因"}
// 只验计数的保留逻辑本身,不走 fail(它要写库)
msg := "这次的原因"
if n := retryCount(rec.ErrorMsg); n > 0 {
msg = withRetryCount(msg, n)
}
if retryCount(msg) != 2 {
t.Errorf("覆盖错误正文后计数应仍为 2,得到 %q", msg)
}
}