L0 核心: - 严格解码 Decode(DisallowUnknownFields) 全覆盖 29 个 DecodeBody 调用点 - DecodeLenient 心跳专用:容忍新字段但回报 unknown_fields - 400 消息列出本端点接受的全部字段(jsonFieldNames 反射 tag) - 日历 status 校验(create 补字段 + update 拦非法值) - 新增 strictdecode_test.go 10 例 + blob/list_test.go 6 例 A-4 附件挂载回滚:checkAttachable 在 CreateMail 前校验,失败按 解挂→释放 relay→删邮件→退预算回滚,幽灵邮件这条路堵住了 A-5 反向 GC:blob.Store.List() 枚举磁盘(跳 .upload-*), SweepUnreferencedBlobs 按 attachments + calendar_attachments 反查, 48h 年龄下限兜上传窗口。已接进每小时 sweep 循环 C 人/Agent 区分:四个读路径 + threadCols 补 from_human / to_human (EXISTS users 判定),models.Mail 加 ToHuman。前端判据从 workspace 启发式改成显式布尔,mailCounterpart/sessionCounterpart 从 session_workspace 取 path(修 dsh@dsh 拼接 bug) 契约文档:SSE new_mail 补 4 字段(in_reply_to/from_human/ permission_mode/permission_enforcement),B-5 加 B-5.6 (Agent→Agent 不转发),B-3.4 MUST 改条件式,心跳补 mode_enforcement + unknown_fields,demo 死链修复 + from_human 检查 验收清单加 Agent→Agent 负向对照项
182 lines
5.8 KiB
Go
182 lines
5.8 KiB
Go
package repo
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
)
|
||
|
||
// 这一组测试钉住「省略 session 位复用默认会话」与「真的新建会话」必须可区分。
|
||
//
|
||
// 事故背景:handler 层曾用 `parentMailID == nil` 判断「是不是新建会话」,
|
||
// 据此决定要不要写往返预算与权限档位。但省略 session 位复用默认会话时
|
||
// parentMailID 也是 nil —— 于是每一封续谈的信都会把这两个字段重置成默认值。
|
||
//
|
||
// 线上实测(修复前):
|
||
//
|
||
// 第一封 to=pi@/tmp/budgetprobe max_rounds=7 → budget_max 7
|
||
// 第二封 to=pi@/tmp/budgetprobe(省略该字段) → budget_max 20 ← 被静默改写
|
||
//
|
||
// 而那段代码的注释本身正在论证这不该发生(「续谈已有会话若也接受这个字段,
|
||
// 每封新信都会悄悄改掉对方正在遵守的预算」)—— 意图是对的,守卫写错了。
|
||
//
|
||
// 修法:FindOrCreateDefaultSessionCreated 额外返回 created,
|
||
// handler 改用它而不是 parentMailID。
|
||
|
||
func TestDefaultSessionFirstCallCreates(t *testing.T) {
|
||
setupTestDB(t)
|
||
ctx := context.Background()
|
||
seedAgent(t, "pi", 20)
|
||
|
||
id, created, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "首封")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !created {
|
||
t.Fatal("从未通信过的 name@path,第一次必须报告 created=true")
|
||
}
|
||
if id.String() == "" {
|
||
t.Fatal("应返回有效会话 id")
|
||
}
|
||
}
|
||
|
||
func TestDefaultSessionReuseReportsNotCreated(t *testing.T) {
|
||
setupTestDB(t)
|
||
ctx := context.Background()
|
||
seedAgent(t, "pi", 20)
|
||
|
||
first, created, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "首封")
|
||
if err != nil || !created {
|
||
t.Fatalf("首封应新建:created=%v err=%v", created, err)
|
||
}
|
||
// 复用的前提是这条会话里有该收件人参与过的邮件(EXISTS 子查询)
|
||
if _, err := CreateMail(ctx, first, nil, "jianf", "", "pi", "/w", "首封", "x", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
second, created2, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "第二封")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if second != first {
|
||
t.Fatalf("第二封应复用同一条默认会话:first=%s second=%s", first, second)
|
||
}
|
||
if created2 {
|
||
t.Fatal("复用已有默认会话时 created 必须为 false —— 这正是预算被冲掉的根因")
|
||
}
|
||
}
|
||
|
||
// 这条是上面那个线上事故的最小复现:走 created 判据时预算不被改写。
|
||
func TestBudgetSurvivesDefaultSessionReuse(t *testing.T) {
|
||
setupTestDB(t)
|
||
ctx := context.Background()
|
||
seedAgent(t, "pi", 20)
|
||
|
||
id, created, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "首封")
|
||
if err != nil || !created {
|
||
t.Fatalf("首封应新建:created=%v err=%v", created, err)
|
||
}
|
||
// 模拟 handler:只有 created 为真才设预算
|
||
if _, err := SetSessionBudget(ctx, id, 7); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := CreateMail(ctx, id, nil, "jianf", "", "pi", "/w", "首封", "x", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
_, created2, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "第二封")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if created2 {
|
||
// 若这里为真,handler 就会重设预算 —— 事故重现
|
||
t.Fatal("复用时 created 为真会让 handler 重设预算")
|
||
}
|
||
|
||
b, err := GetSessionBudget(ctx, id)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if b.Max != 7 {
|
||
t.Fatalf("续谈不得改写预算:want 7, got %d", b.Max)
|
||
}
|
||
}
|
||
|
||
// 档位与预算同一个判据,一起钉住:plan 档不能因为第二封信而升成 workspace。
|
||
func TestPermissionModeSurvivesDefaultSessionReuse(t *testing.T) {
|
||
setupTestDB(t)
|
||
ctx := context.Background()
|
||
seedAgent(t, "pi", 20)
|
||
|
||
id, created, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "首封")
|
||
if err != nil || !created {
|
||
t.Fatalf("首封应新建:created=%v err=%v", created, err)
|
||
}
|
||
if _, err := SetSessionPermissionMode(ctx, id, "plan"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := CreateMail(ctx, id, nil, "jianf", "", "pi", "/w", "首封", "x", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
_, created2, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "第二封")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if created2 {
|
||
t.Fatal("复用时 created 为真会让 handler 把档位重置成默认档")
|
||
}
|
||
|
||
if got := SessionPermissionMode(ctx, id); got != "plan" {
|
||
t.Fatalf("续谈不得改写档位:want plan, got %s", got)
|
||
}
|
||
}
|
||
|
||
// 不同工作目录是不同的默认会话,两边各自新建。
|
||
// 这条防的是「把 created 实现成一个全局标志」之类的偷懒写法。
|
||
func TestDefaultSessionPerWorkspace(t *testing.T) {
|
||
setupTestDB(t)
|
||
ctx := context.Background()
|
||
seedAgent(t, "pi", 20)
|
||
|
||
a, createdA, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w1", "jianf", "甲")
|
||
if err != nil || !createdA {
|
||
t.Fatalf("/w1 应新建:%v %v", createdA, err)
|
||
}
|
||
if _, err := CreateMail(ctx, a, nil, "jianf", "", "pi", "/w1", "甲", "x", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
b, createdB, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w2", "jianf", "乙")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !createdB {
|
||
t.Fatal("/w2 是另一个工作目录,应当另建一条默认会话")
|
||
}
|
||
if a == b {
|
||
t.Fatal("不同工作目录不该共用同一条默认会话")
|
||
}
|
||
}
|
||
|
||
// 旧签名仍在别处被调用,保持行为不变(只是丢掉 created)。
|
||
func TestLegacyWrapperStillWorks(t *testing.T) {
|
||
setupTestDB(t)
|
||
ctx := context.Background()
|
||
seedAgent(t, "pi", 20)
|
||
|
||
id, err := FindOrCreateDefaultSession(ctx, "pi", "/w", "jianf", "首封")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := CreateMail(ctx, id, nil, "jianf", "", "pi", "/w", "首封", "x", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
again, err := FindOrCreateDefaultSession(ctx, "pi", "/w", "jianf", "第二封")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if again != id {
|
||
t.Fatalf("包装函数应与原行为一致:%s vs %s", id, again)
|
||
}
|
||
}
|