chore: directory migration - gateway→server, web→client/electron

This commit is contained in:
2026-09-08 19:16:35 +08:00
parent fd9f99a3f9
commit f9d757b5e5
243 changed files with 5095 additions and 228 deletions

View File

@ -0,0 +1,111 @@
package scheduler
import (
"strings"
"testing"
"time"
"github.com/agentmail/gateway/internal/models"
)
func TestRenderReminder(t *testing.T) {
at := time.Date(2026, 9, 4, 9, 30, 0, 0, time.Local)
e := models.CalendarEvent{
Title: "每日站会",
Description: "同步昨天进展与今天计划",
EventTime: at,
}
t.Run("三个变量都替换", func(t *testing.T) {
got := RenderReminder("日程提醒:{title}\n时间{time}\n{description}", e)
for _, want := range []string{"每日站会", "2026-09-04 09:30", "同步昨天进展与今天计划"} {
if !strings.Contains(got, want) {
t.Errorf("渲染结果缺 %q\n%s", want, got)
}
}
if strings.Contains(got, "{") {
t.Errorf("仍有未替换的占位符:\n%s", got)
}
})
t.Run("同一变量出现多次全部替换", func(t *testing.T) {
// ReplaceAll 而非 Replace模板里写两遍 {title} 时
// 只替换第一处会让 Agent 收到一封半成品邮件。
got := RenderReminder("{title} —— 请开始 {title}", e)
if strings.Contains(got, "{title}") {
t.Errorf("第二处 {title} 未替换:%s", got)
}
})
t.Run("空模板不产生占位符残留", func(t *testing.T) {
if got := RenderReminder("", e); got != "" {
t.Errorf("空模板应渲染成空串,得到 %q", got)
}
})
t.Run("没有变量的模板原样返回", func(t *testing.T) {
const plain = "该跑测试了"
if got := RenderReminder(plain, e); got != plain {
t.Errorf("纯文本模板应原样返回,得到 %q", got)
}
})
t.Run("描述为空时不留下空行以外的痕迹", func(t *testing.T) {
e2 := e
e2.Description = ""
got := RenderReminder("{title}|{description}|", e2)
if got != "每日站会||" {
t.Errorf("空描述应替换成空串,得到 %q", got)
}
})
// {time} 必须是本地时间。DSN 带 _timezone=UTC从库里读回的 EventTime
// 是 UTC不转本地就会把人在 +0800 输入的 14:30 写成 06:30
// 而前端预览用的是本地时间 —— 两边差 8 小时且都不报错。
t.Run("time 用本地时区而非 UTC", func(t *testing.T) {
// 刻意构造一个 UTC 时刻(模拟从库里 Scan 出来的样子)
utcEvent := models.CalendarEvent{
Title: "跨时区检查",
EventTime: time.Date(2026, 9, 3, 6, 30, 0, 0, time.UTC),
}
got := RenderReminder("{time}", utcEvent)
want := time.Date(2026, 9, 3, 6, 30, 0, 0, time.UTC).Local().Format("2006-01-02 15:04")
if got != want {
t.Errorf("{time} = %q期望本地时间 %q", got, want)
}
})
// 同一时刻无论以哪个时区的 Location 传进来,渲染结果必须一致 ——
// 它代表的是「墙上时钟的那一刻」,与 Location 的表示方式无关。
t.Run("同一时刻不同 Location 渲染一致", func(t *testing.T) {
base := time.Date(2026, 9, 3, 6, 30, 0, 0, time.UTC)
a := RenderReminder("{time}", models.CalendarEvent{EventTime: base})
b := RenderReminder("{time}", models.CalendarEvent{EventTime: base.Local()})
if a != b {
t.Errorf("UTC 与 Local 表示同一时刻却渲染出不同结果:%q vs %q", a, b)
}
})
}
func TestShort(t *testing.T) {
// 日志里截前 8 位;短 id测试里可能出现不能 panic
if got := short("0123456789abcdef"); got != "01234567" {
t.Errorf("长 id 应截断成 8 位,得到 %q", got)
}
if got := short("abc"); got != "abc" {
t.Errorf("短 id 应原样返回,得到 %q", got)
}
if got := short(""); got != "" {
t.Errorf("空串应原样返回,得到 %q", got)
}
}
func TestStartStopIdempotent(t *testing.T) {
// Stop 在没启动时被调defer 里必然发生)不该 panic
// Start 两次也不该泄漏 goroutine第二次先停旧的
Stop()
Start()
Start()
Stop()
Stop()
}