fix(判据): 农历推进的起点必须在未来 —— 否则 advanceToFuture 跳过过期月份必假红
## 现象
部署被测试闸拦下(这正是纪律该做的事):`TestAdvanceRecurrenceLunar` 报
「间隔 58 天不像一个农历月」。已确认与本次 SSE 改动无关
(把我的改动全部 stash 后它同样红)。
## 根因(实测复算,不是推测)
`AdvanceRecurrence` 走 `advanceToFuture`,职责是「推进到**未来**」:
已经过去的农历月会被跳过(`if cur.After(now) { return }` 那个循环)。
原起点写死 `2026-09-03`,而今天已是 10-02 ⇒ 那个农历月(10-02)已过去
⇒ 循环再推一个月。探针实测:
起点 2026-09-03 ⇒ 落点 2026-10-31 间隔 58 天 ← 旧起点,今天跑必红
起点 2026-10-03 ⇒ 落点 2026-11-01 间隔 29 天 ← 明天,正确
农历库本身是对的:直接调 `AddMonths(1).ToSolar()` 得到的落点恰好 29 天。
所以**不是代码缺陷,是判据的期望依赖了「今天离起点不到一个月」**这个
随日期漂移的前提。
## 修法
起点改为「明天」起算(不写死具体日期 ⇒ 明年跑也成立),
农历日的期望也跟着起点走。
★ 不用 `time.Now().AddDate(0,1,0)` 那种相对写法:农历月 29/30 天不定,
起点落在月末时下一个同农历日可能被夹(commit 3459605 记的同族假红)。
明天起算同时满足「确保在未来」与「落点就是下一个农历月」。
## ★★ 修判据时差点削弱了它(变异验证抓出来的)
把起点改成明天后判据绿了,但我立刻做变异验证:
变异:advanceToFuture 不跳过已过期月份 ⇒ TestAdvanceRecurrenceLunar **全绿**
因为起点在未来,第一个落点本来就在未来,循环与单步没有区别 ——
**我修好了假红,却顺手删掉了「跳过过期」这个真行为的判别力。**
补了一格:用**已过期 70 天**的起点单独钉它,落点必须在未来,
否则「每次扫描都重复触发同一封提醒」。变异重测 ⇒ 红 1 格。
这是同一天内第二次判据自身缺陷(第一次是 SSE 那格只查文本不查控制流)。
**改判据后必须变异验证判别力还在**,否则就是用改测试掩盖问题。
This commit is contained in:
@ -623,7 +623,24 @@ func TestAdvanceRecurrenceLunar(t *testing.T) {
|
||||
setupTestDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
start := time.Date(2026, 9, 3, 9, 0, 0, 0, time.Local)
|
||||
// ★ 2026-10-02:起点必须是**未来**的,否则这条判据必红(同族第三次)。
|
||||
//
|
||||
// `AdvanceRecurrence` 走的是 `advanceToFuture`,它的职责是「推进到**未来**」:
|
||||
// 已经过去的农历月会被跳过(`if cur.After(now) { return }` 那个循环)。
|
||||
//
|
||||
// 原起点写死 `2026-09-03`,而今天已是 10-02 ⇒ 那个农历月(10-02)已过去
|
||||
// ⇒ 循环再推一个月 ⇒ 落点 10-31,间隔 **58 天**,与「一个农历月」的断言矛盾。
|
||||
//
|
||||
// 实测(本次修复时跑的探针):
|
||||
// 起点 2026-09-03 ⇒ 落点 2026-10-31 间隔 58 天 ← 旧起点,今天跑必红
|
||||
// 起点 2026-10-03 ⇒ 落点 2026-11-01 间隔 29 天 ← 明天,正确
|
||||
//
|
||||
// 为什么不能用 `time.Now().AddDate(0,1,0)` 那种「相对现在」写法:
|
||||
// 农历月 29/30 天不定,起点落在月末时下一个同农历日可能被夹
|
||||
// (见 commit 3459605 记的同族问题:那是**夹到短月**导致的另一种假红)。
|
||||
// 这里要的是「起点确保在未来,且落点就是下一个农历月」——
|
||||
// 明天起算就同时满足两者,且不依赖具体日期。
|
||||
start := time.Now().AddDate(0, 0, 1).Truncate(time.Hour).Add(9 * time.Hour)
|
||||
e := seedEvent(t, &models.CalendarEvent{
|
||||
Title: "农历每月十五(这里用廿二)",
|
||||
EventTime: start,
|
||||
@ -645,15 +662,46 @@ func TestAdvanceRecurrenceLunar(t *testing.T) {
|
||||
if !after.EventTime.After(start) {
|
||||
t.Errorf("event_time 未前进:%v", after.EventTime)
|
||||
}
|
||||
// 农历日保持
|
||||
if d := lunar.FromSolar(after.EventTime); d.Day != 22 {
|
||||
t.Errorf("农历日变成 %d,期望 22(%s)", d.Day, d.String())
|
||||
// 农历日保持(起点是明天,那个日就是起点的农历日)
|
||||
wantLunarDay := lunar.FromSolar(start).Day
|
||||
if d := lunar.FromSolar(after.EventTime); d.Day != wantLunarDay {
|
||||
t.Errorf("农历日变成 %d,期望 %d(%s)", d.Day, wantLunarDay, d.String())
|
||||
}
|
||||
// 公历间隔应在一个农历月内
|
||||
gap := int(after.EventTime.Sub(start).Hours() / 24)
|
||||
if gap < 28 || gap > 31 {
|
||||
t.Errorf("间隔 %d 天不像一个农历月", gap)
|
||||
}
|
||||
|
||||
/*
|
||||
★ 补这一格(2026-10-02):起点改成「明天」之后,判据的**判别力**下降了。
|
||||
|
||||
实测:把 `advanceToFuture` 改成「不跳过已过期的农历月」,
|
||||
`TestAdvanceRecurrenceLunar` 依然**全绿** —— 因为起点在未来,
|
||||
第一个落点本来就在未来,循环与单步没有区别。
|
||||
|
||||
而「跳过过期」是**真行为**:`AdvanceRecurrence` 存在的意义就是
|
||||
把过期事件推到将来(否则每次扫描都会重复触发同一封信)。
|
||||
下面这格用一个**已过期**的起点单独钉它。
|
||||
*/
|
||||
past := time.Now().AddDate(0, 0, -70)
|
||||
e2 := seedEvent(t, &models.CalendarEvent{
|
||||
Title: "过期起点:必须被推到未来",
|
||||
EventTime: past,
|
||||
Recurrence: models.RecurLunarMonthly,
|
||||
})
|
||||
if _, err := AdvanceRecurrence(ctx, e2.EventID); err != nil {
|
||||
t.Fatalf("过期起点推进: %v", err)
|
||||
}
|
||||
after2, err := GetCalendarEvent(ctx, e2.EventID)
|
||||
if err != nil {
|
||||
t.Fatalf("读回: %v", err)
|
||||
}
|
||||
if !after2.EventTime.After(time.Now()) {
|
||||
t.Errorf("★ 起点已过期时,落点 %s 仍不在未来 —— "+
|
||||
"advanceToFuture 必须跳过已过去的农历月,否则每次扫描都重复触发同一封提醒",
|
||||
after2.EventTime.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 多收件人 ───
|
||||
|
||||
Reference in New Issue
Block a user