Files
MailUI4Agents/server/internal/repo/defaultsession_test.go

270 lines
9.1 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 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)
}
}
// ─── 预算不被冲的回归用例P0 第一项P3 钉死) ───
//
// 守卫原本是 `parentMailID == nil`(表示新建),但省略 session 位复用默认会话时
// parentMailID 也是 nil —— 于是「仅在新建时生效」的字段(预算、档位)在每封
// 省略 session 位的信上都被重写了。实测:第一封 max_rounds=7 → 第二封省略该
// 字段 → 预算被静默改成默认的 20。
//
// 修法handler 改用 FindOrCreateDefaultSessionCreated 返回的 `created` 判据。
// 本测试钉死「复用默认会话时 created=false」这一事实让守卫不会倒退回去。
func TestDefaultSessionReuseBudgetNotReset(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)
}
// 模拟发信路径:新建会话时定预算为 7低于默认 20
if _, err := SetSessionBudget(ctx, first, 7); err != nil {
t.Fatal(err)
}
// 复用的前提是会话里有该收件人参与过的邮件
if _, err := CreateMail(ctx, first, nil, "jianf", "", "pi", "/w", "首封", "x", nil); err != nil {
t.Fatal(err)
}
// 第二封省略 session 位 → 复用默认会话created 必须为 false
second, created2, err := FindOrCreateDefaultSessionCreated(ctx, "pi", "/w", "jianf", "第二封")
if err != nil {
t.Fatal(err)
}
if second != first {
t.Fatalf("第二封应复用同一会话:%s vs %s", first, second)
}
if created2 {
t.Fatal("复用默认会话时 created 必须为 false否则预算会被默认值冲掉")
}
// 既然 created2=false发信路径不会调 SetSessionBudget → 预算仍为 7
b, err := GetSessionBudget(ctx, first)
if err != nil {
t.Fatal(err)
}
if b.Max != 7 {
t.Errorf("预算被冲掉got max=%dwant 7复用不应重设", b.Max)
}
}
// ─── 档位同样不被冲的回归用例 ───
//
// 与预算同理:复用默认会话时档位也不该被重设成默认档。
// 人指定 plan 档后,第二封信省略 session 位复用同一条会话 →
// created=false → SetSessionPermissionMode 不被调 → 档位仍为 plan。
func TestDefaultSessionReusePermissionModeNotReset(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)
}
// 新建会话时定档位为 plan比默认 workspace 更严)
if _, err := SetSessionPermissionMode(ctx, first, "plan"); err != nil {
t.Fatal(err)
}
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("第二封应复用同一会话:%s vs %s", first, second)
}
if created2 {
t.Fatal("复用默认会话时 created 必须为 false否则档位会被默认值冲掉")
}
mode := SessionPermissionMode(ctx, first)
if mode != "plan" {
t.Errorf("档位被冲掉got %qwant plan复用不应重设", mode)
}
}