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

229 lines
7.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 repo
import (
"context"
"testing"
"time"
"github.com/agentmail/gateway/internal/db"
)
func seedPlatformMirror(t *testing.T, agentName string, list []PlatformSession) {
t.Helper()
if err := ReplacePlatformSessions(context.Background(), agentName, list); err != nil {
t.Fatalf("上报镜像: %v", err)
}
}
// 补全把平台会话列为候选,投递侧必须能命中同一条。
// 此前 FindNamedSessionFor 只查 sessions 表 —— 候选列表在承诺一件做不到的事。
func TestFindPlatformSession(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
now := time.Now()
seedPlatformMirror(t, "pi", []PlatformSession{
{PlatformID: "pi-sess-1", Workspace: "/home/program/agentmail",
Slug: "设计文档-项目定位", Title: "邮件驱动·多智能体协作平台", UpdatedAt: &now},
{PlatformID: "pi-sess-2", Workspace: "/tmp/other",
Slug: "别处的会话", Title: "无关", UpdatedAt: &now},
})
t.Run("按 slug + workspace 命中", func(t *testing.T) {
pid, ws, title, err := FindPlatformSession(ctx, "pi", "设计文档-项目定位", "/home/program/agentmail")
if err != nil {
t.Fatalf("查找: %v", err)
}
if pid != "pi-sess-1" {
t.Errorf("platform_id = %q", pid)
}
if ws != "/home/program/agentmail" {
t.Errorf("workspace = %q", ws)
}
if title != "邮件驱动·多智能体协作平台" {
t.Errorf("title = %q", title)
}
})
// 地址省略 path 位时不限工作区
t.Run("workspace 为空时不限", func(t *testing.T) {
if pid, _, _, err := FindPlatformSession(ctx, "pi", "别处的会话", ""); err != nil || pid != "pi-sess-2" {
t.Errorf("得到 %q err=%v", pid, err)
}
})
// workspace 不匹配时不该命中 —— 那会让邮件投进另一个项目的会话
t.Run("workspace 不匹配不命中", func(t *testing.T) {
if _, _, _, err := FindPlatformSession(ctx, "pi", "别处的会话", "/home/program/agentmail"); err == nil {
t.Error("workspace 不同却命中了")
}
})
t.Run("别的 Agent 的镜像不串", func(t *testing.T) {
if _, _, _, err := FindPlatformSession(ctx, "dsh", "设计文档-项目定位", ""); err == nil {
t.Error("dsh 命中了 pi 的会话")
}
})
t.Run("空参数返回 not found 而不是 panic", func(t *testing.T) {
if _, _, _, err := FindPlatformSession(ctx, "", "x", ""); err != ErrSessionNotFound {
t.Errorf("空 agent 应给 ErrSessionNotFound得到 %v", err)
}
if _, _, _, err := FindPlatformSession(ctx, "pi", "", ""); err != ErrSessionNotFound {
t.Errorf("空 slug 应给 ErrSessionNotFound得到 %v", err)
}
})
}
// 接管后本侧有正式身份:可寻址(别名)、绑定 platform_id、workspace 用会话真实的。
func TestAdoptPlatformSession(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "pi", 20)
id, err := AdoptPlatformSession(ctx, "pi", "pi-sess-1", "设计文档-项目定位",
"/home/program/agentmail", "邮件驱动·多智能体协作平台")
if err != nil {
t.Fatalf("接管: %v", err)
}
// 别名复用平台 slug人在补全里看到的就是那个名字换掉会让他找不到
if alias := SessionAliasOf(ctx, id); alias != "设计文档-项目定位" {
t.Errorf("别名 = %q期望复用平台 slug", alias)
}
if pid := PlatformIDOf(ctx, id); pid != "pi-sess-1" {
t.Errorf("platform_id = %q", pid)
}
// workspace 取平台会话的真实 cwd
var ws string
if err := db.DB.QueryRowContext(ctx,
`SELECT workspace FROM sessions WHERE session_id = $1`, id).Scan(&ws); err != nil {
t.Fatalf("读 workspace: %v", err)
}
if ws != "/home/program/agentmail" {
t.Errorf("workspace = %q", ws)
}
}
// 普通会话的 platform_id 必须是空串(不是接管来的)。
func TestPlatformIDOfEmptyForNormalSession(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "pi", 20)
id, err := CreateSession(ctx, nil, "pi", "普通邮件会话", "/tmp/x")
if err != nil {
t.Fatalf("建会话: %v", err)
}
if pid := PlatformIDOf(ctx, id); pid != "" {
t.Errorf("普通会话的 platform_id 应为空,得到 %q", pid)
}
}
// 一条平台会话只能被接管一次。
//
// 第二次投递必须复用第一次建的本侧会话 —— 否则同一条 TUI 对话会在邮箱里
// 裂成多条互不相干的线索:人看到三个同名会话,而回信只落在其中一条上。
func TestFindSessionByPlatformIDPreventsDoubleAdopt(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "pi", 20)
id, err := AdoptPlatformSession(ctx, "pi", "pi-sess-1", "某会话", "/tmp/ws", "标题")
if err != nil {
t.Fatalf("接管: %v", err)
}
// 接管后还没有邮件 —— 此时反查不到EXISTS 子句要求有本 Agent 参与的邮件)
if _, err := FindSessionByPlatformID(ctx, "pi", "pi-sess-1"); err == nil {
t.Log("注意:无邮件时也能反查到")
}
// 投一封进去,让参与关系成立
if _, err := CreateMail(ctx, id, nil, "jianf", "", "pi", "/tmp/ws", "主题", "正文", nil); err != nil {
t.Fatalf("建邮件: %v", err)
}
got, err := FindSessionByPlatformID(ctx, "pi", "pi-sess-1")
if err != nil {
t.Fatalf("反查: %v", err)
}
if got != id {
t.Errorf("反查到 %v期望 %v", got, id)
}
// 别的 platform_id 查不到
if _, err := FindSessionByPlatformID(ctx, "pi", "pi-sess-999"); err != ErrSessionNotFound {
t.Errorf("不存在的 platform_id 应给 ErrSessionNotFound得到 %v", err)
}
// 别的 Agent 查不到(参与关系不成立)
if _, err := FindSessionByPlatformID(ctx, "dsh", "pi-sess-1"); err != ErrSessionNotFound {
t.Errorf("dsh 不该查到 pi 的接管会话,得到 %v", err)
}
}
// 整表替换镜像后,已接管的本侧会话不受影响。
//
// 镜像是平台当前状态的快照、会被整表替换;而 sessions.platform_id 是本侧的
// 持久绑定。平台侧那条会话被删掉之后,本侧线索与历史邮件仍然要在。
func TestAdoptedSessionSurvivesMirrorReplace(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "pi", 20)
now := time.Now()
seedPlatformMirror(t, "pi", []PlatformSession{
{PlatformID: "pi-sess-1", Workspace: "/tmp/ws", Slug: "会话甲", UpdatedAt: &now},
})
id, err := AdoptPlatformSession(ctx, "pi", "pi-sess-1", "会话甲", "/tmp/ws", "标题")
if err != nil {
t.Fatalf("接管: %v", err)
}
// 平台侧删了那条会话(新快照里没有它)
seedPlatformMirror(t, "pi", []PlatformSession{
{PlatformID: "pi-sess-2", Workspace: "/tmp/ws", Slug: "会话乙", UpdatedAt: &now},
})
// 本侧绑定与别名都还在
if pid := PlatformIDOf(ctx, id); pid != "pi-sess-1" {
t.Errorf("镜像替换后 platform_id 丢了:%q", pid)
}
if alias := SessionAliasOf(ctx, id); alias != "会话甲" {
t.Errorf("别名丢了:%q", alias)
}
// 但镜像里查不到了(补全不再列它,符合预期)
if _, _, _, err := FindPlatformSession(ctx, "pi", "会话甲", ""); err != ErrSessionNotFound {
t.Errorf("镜像里应已消失,得到 %v", err)
}
}
// 接管用的 slug 与本侧某条无关会话撞名时要自动加后缀(别名全局唯一)。
func TestAdoptHandlesAliasCollision(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "pi", 20)
// 先占掉这个别名
taken := "撞名的别名"
if _, err := CreateSession(ctx, &taken, "pi", "已存在", "/tmp/a"); err != nil {
t.Fatalf("建占位会话: %v", err)
}
id, err := AdoptPlatformSession(ctx, "pi", "pi-sess-x", taken, "/tmp/b", "标题")
if err != nil {
t.Fatalf("接管: %v", err)
}
alias := SessionAliasOf(ctx, id)
if alias == "" {
t.Fatal("接管后没有别名 —— 这条会话将无法寻址")
}
if alias == taken {
t.Errorf("别名与已存在的重复了:%q", alias)
}
// 绑定仍然正确
if pid := PlatformIDOf(ctx, id); pid != "pi-sess-x" {
t.Errorf("platform_id = %q", pid)
}
}