chore: directory migration - gateway→server, web→client/electron
This commit is contained in:
119
server/internal/repo/nearesthuman_test.go
Normal file
119
server/internal/repo/nearesthuman_test.go
Normal file
@ -0,0 +1,119 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/agentmail/gateway/internal/db"
|
||||
)
|
||||
|
||||
// 权限询问的决策者必须是人:Agent 收不到 SendToUser,桥的 await 永不 resolve。
|
||||
// 生产事故:pi 把任务派给自己的另一条会话 → 那条会话要跑 bash → 权限邮件发给 "pi"
|
||||
// → pi 不是人类用户 → 整条会话永久阻塞。
|
||||
//
|
||||
// 修法是顺着会话的邮件链上溯找最近的人类 —— 派活的人才是该点头的人。
|
||||
func TestNearestHumanInThread(t *testing.T) {
|
||||
setupTestDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
mustExec(t, ctx, `INSERT INTO users (username, display_name, password_hash, role)
|
||||
VALUES ('alice','Alice','x','user')`)
|
||||
for _, a := range []string{"opencode", "dsh", "pi"} {
|
||||
if err := CreateOrUpdateAgent(ctx, a, "s", "test", nil); err != nil {
|
||||
t.Fatalf("注册 %s: %v", a, err)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("沿链上溯找到派活的人", func(t *testing.T) {
|
||||
sid, err := CreateSession(ctx, nil, "opencode", "任务链", "/home")
|
||||
if err != nil {
|
||||
t.Fatalf("建会话: %v", err)
|
||||
}
|
||||
// alice → opencode → dsh,dsh 触发权限询问
|
||||
m1, err := CreateMail(ctx, sid, nil, "alice", "", "opencode", "", "任务", "请帮忙", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("封1: %v", err)
|
||||
}
|
||||
m2, err := CreateMail(ctx, sid, &m1, "opencode", "", "dsh", "", "转派", "你来看", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("封2: %v", err)
|
||||
}
|
||||
if _, err := CreateMail(ctx, sid, &m2, "dsh", "", "opencode", "", "进展", "做了一半", nil); err != nil {
|
||||
t.Fatalf("封3: %v", err)
|
||||
}
|
||||
|
||||
for _, agent := range []string{"dsh", "opencode"} {
|
||||
got, err := NearestHumanInThread(ctx, sid, agent)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", agent, err)
|
||||
}
|
||||
if got != "alice" {
|
||||
t.Errorf("%s 触发权限时应路由到 alice,得到 %q", agent, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("全 Agent 链返回空串", func(t *testing.T) {
|
||||
// 没有人类参与的链条:调用方据此拒绝请求,而不是转给一个
|
||||
// 对上下文一无所知的管理员。
|
||||
sid, err := CreateSession(ctx, nil, "opencode", "纯 Agent", "/tmp")
|
||||
if err != nil {
|
||||
t.Fatalf("建会话: %v", err)
|
||||
}
|
||||
m1, err := CreateMail(ctx, sid, nil, "opencode", "", "dsh", "", "干活", "go", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("封1: %v", err)
|
||||
}
|
||||
if _, err := CreateMail(ctx, sid, &m1, "dsh", "", "opencode", "", "好", "ok", nil); err != nil {
|
||||
t.Fatalf("封2: %v", err)
|
||||
}
|
||||
|
||||
got, err := NearestHumanInThread(ctx, sid, "dsh")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "" {
|
||||
t.Errorf("链上没有人类时应返回空串,得到 %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("skipAgent 是自己时不会把自己当人", func(t *testing.T) {
|
||||
// pi 给自己的另一条会话派活正是事故场景:链上只有 pi 一个名字。
|
||||
sid, err := CreateSession(ctx, nil, "pi", "自派", "/home")
|
||||
if err != nil {
|
||||
t.Fatalf("建会话: %v", err)
|
||||
}
|
||||
if _, err := CreateMail(ctx, sid, nil, "pi", "", "pi", "", "拆任务", "自己干", nil); err != nil {
|
||||
t.Fatalf("封1: %v", err)
|
||||
}
|
||||
|
||||
got, err := NearestHumanInThread(ctx, sid, "pi")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "" {
|
||||
t.Errorf("自派链上没有人类,应返回空串,得到 %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("空会话不报错", func(t *testing.T) {
|
||||
sid, err := CreateSession(ctx, nil, "opencode", "空", "/tmp")
|
||||
if err != nil {
|
||||
t.Fatalf("建会话: %v", err)
|
||||
}
|
||||
got, err := NearestHumanInThread(ctx, sid, "opencode")
|
||||
if err != nil {
|
||||
t.Fatalf("空会话应返回空串而非报错,得到 err=%v", err)
|
||||
}
|
||||
if got != "" {
|
||||
t.Errorf("空会话应返回空串,得到 %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func mustExec(t *testing.T, ctx context.Context, q string, args ...any) {
|
||||
t.Helper()
|
||||
if _, err := db.DB.ExecContext(ctx, q, args...); err != nil {
|
||||
t.Fatalf("exec %s: %v", q, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user