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) } }