Files
MailUI4Agents/server/internal/handler/forward_test.go
JianFeeeee 7ff20feffe fix(forward): 转发引用里的「时间」按本地时区渲染(原先是 UTC 原样)
用户(转发 zcode 的信给我):「它说的它发的这个邮件你收到了吗?它说没有收到回复……」——
问题不在那封信,而在它引用的时间:用户看到的「2026-09-15 01:23:19」其实是 UTC,
本地时间应为 09:23:19(本机 UTC+8)。

根因:quoteBody 用 `m.CreatedAt.Format(...)` 直接格式化(库里存 UTC),而界面各处都转本地;
同一族的正确写法在 scheduler/calendar.go 里(`e.EventTime.Local().Format(...)`)。

★ 顺带发现一条**旧断言一直在保护这个 bug**:TestQuoteBodyPrefixesEveryLine 里写死了
「2026-09-02 10:30:00」(fixture 是 time.UTC)——已改为按本地计算,不再写死字面量
(与前几天 nav-merge.test.mjs 那条同一形态:断言钉着错的意图)。

判据:新增 TestQuoteBodyTimeIsLocal(引用时间必须等于 CreateAt.Local() 渲染,且不得等于
UTC 原样)。变异:去掉 .Local() → 2 条判据变红(新加的 + 改过的旧断言);复原 → 包内全绿。
注:该判据在 TZ=UTC 的机器上是空转的(本地==UTC),本机 UTC+8 会真红。

▲ 部署未完成:/opt/agentmail 现在对我不可写(touch 都被拒;uid/能力问题,08:57 之后出现),
加上一个 08:57 留下的陈旧 /opt/agentmail/.deploy.lock 挡在 redeploy-gateway.sh 前面。
需要能写那里的人跑:rm -f /opt/agentmail/.deploy.lock && bash deploy/redeploy-gateway.sh
2026-09-15 09:36:47 +08:00

105 lines
3.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 handler
import (
"strings"
"testing"
"time"
"github.com/agentmail/gateway/internal/models"
"github.com/google/uuid"
)
// 转发主题不能无限叠加 Fwd: 前缀,否则转发几轮后主题栏全是前缀。
func TestForwardSubject(t *testing.T) {
cases := []struct{ custom, original, want string }{
{"", "修复登录态", "Fwd: 修复登录态"},
{"", "Fwd: 修复登录态", "Fwd: 修复登录态"}, // 已有前缀不再叠加
{"自定义主题", "修复登录态", "自定义主题"},
{" ", "修复登录态", "Fwd: 修复登录态"}, // 全空白视为未指定
}
for _, c := range cases {
if got := forwardSubject(c.custom, c.original); got != c.want {
t.Errorf("forwardSubject(%q, %q) = %q, want %q", c.custom, c.original, got, c.want)
}
}
}
// 引用块必须逐行加 "> ":原文含代码块或列表时,
// 只有逐行前缀才能在任何 Markdown 渲染器里保持引用语义。
func TestQuoteBodyPrefixesEveryLine(t *testing.T) {
m := &models.Mail{
ID: uuid.New(),
FromName: "opencode",
FromWorkspace: "/root",
Subject: "巡检结果",
Body: "第一行\n\n```go\nfmt.Println(1)\n```\n- 列表项",
CreatedAt: time.Date(2026, 9, 2, 10, 30, 0, 0, time.UTC),
CCList: []models.Address{
{Name: "pi", Path: "root", Raw: "pi@root.new"},
},
}
out := quoteBody(m)
for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
if line == "---" || line == "" {
continue
}
if !strings.HasPrefix(line, ">") {
t.Errorf("引用块出现未加前缀的行: %q", line)
}
}
// 元信息必须齐全,否则收件人不知道这封转发的来路
// ★ 2026-09-15:这里原先写死 "2026-09-02 10:30:00"(UTC 原样),而 fixture 是 time.UTC ——
// 等于这条断言一直在**保护那个 bug**(引用块显示 UTC 时间)。时间一项按本地算,不写死字面量。
for _, want := range []string{"opencode@/root", "巡检结果", m.CreatedAt.Local().Format("2006-01-02 15:04:05"), "pi@root.new"} {
if !strings.Contains(out, want) {
t.Errorf("引用块缺少 %q\n%s", want, out)
}
}
// 原文正文本身要在引用里
if !strings.Contains(out, "> fmt.Println(1)") {
t.Errorf("原文代码行未被引用:\n%s", out)
}
}
// 无抄送时不该渲染出空的「抄送」行。
func TestQuoteBodyOmitsEmptyCC(t *testing.T) {
m := &models.Mail{
FromName: "admin",
Subject: "x",
Body: "y",
CreatedAt: time.Now(),
}
if strings.Contains(quoteBody(m), "抄送") {
t.Error("无抄送时不应出现「抄送」行")
}
}
// TestQuoteBodyTimeIsLocal —— 转发引用里的「时间」必须是**本地**时间,不是 UTC 原样。
//
// 2026-09-15 用户报:引用块显示 01:23:19,而实际发生时间是 09:23:19(本机 UTC+8)。
// 库里存 UTC、界面各处都转本地,只有 quoteBody 漏了 .Local()。
//
// ★ 这条判据在 **TZ=UTC 的机器上是空转的**(本地 == UTC,两种写法同结果)——
// 本机是 UTC+8,所以这里真会红。CI 若在 UTC 下跑,它是"只能验形态"的那一类,
// 形态判据另有一条:不得与 UTC 原样相等(当两者不同时)。
func TestQuoteBodyTimeIsLocal(t *testing.T) {
// 取一个"本地与 UTC 不同"的瞬间来测(否则本判据无从区分)
m := &models.Mail{
FromName: "zcode",
Subject: "时间判定",
CreatedAt: time.Date(2026, 9, 15, 1, 23, 19, 0, time.UTC),
}
got := quoteBody(m)
want := m.CreatedAt.Local().Format("2006-01-02 15:04:05")
if !strings.Contains(got, "> **时间** "+want) {
t.Fatalf("引用里的时间不是本地时间:想要 %q,实际渲染:\n%s", want, got)
}
if u := m.CreatedAt.UTC().Format("2006-01-02 15:04:05"); u != want && strings.Contains(got, "> **时间** "+u) {
t.Fatalf("引用里出现的是 UTC 原样 %q(应为本地 %q)", u, want)
}
}