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

136 lines
4.2 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"
"errors"
"testing"
"github.com/agentmail/gateway/internal/db"
)
// RecipientDeliverable 是「邮件会不会进黑洞」的唯一判据。
//
// 事故背景:发信路径原来只校验地址语法、调用权限与会话别名,从不问
// 「这个名字存在吗」。实测发给已彻底删除的 remotebot 返回 200邮件入库、
// 分配了 20 个来回预算、建好会话,而那一端永远不会有人读。发件人看到 200
// 和一个 session_id以为送出去了 —— 静默丢件比报错严重,报错能立刻改,
// 静默丢件要等对方追问才发现。
func TestRecipientDeliverable_Human(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
if _, err := db.DB.ExecContext(ctx,
`INSERT INTO users (username, password_hash) VALUES ('alice', 'x')`); err != nil {
t.Fatalf("seed user: %v", err)
}
if err := RecipientDeliverable(ctx, "alice"); err != nil {
t.Fatalf("人类用户应当可达,得到: %v", err)
}
}
func TestRecipientDeliverable_OnlineAgent(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "pi", 20)
if err := RecipientDeliverable(ctx, "pi"); err != nil {
t.Fatalf("在册且未停用的 Agent 应当可达,得到: %v", err)
}
}
func TestRecipientDeliverable_UnknownName(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
err := RecipientDeliverable(ctx, "ghost")
if !errors.Is(err, ErrRecipientUnknown) {
t.Fatalf("不存在的收件人应当返回 ErrRecipientUnknown得到: %v", err)
}
}
// 删除后立刻不可达 —— 这正是本次事故的场景。
func TestRecipientDeliverable_AfterDelete(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "remotebot", 20)
if err := RecipientDeliverable(ctx, "remotebot"); err != nil {
t.Fatalf("删除前应当可达,得到: %v", err)
}
if _, err := DeleteAgent(ctx, "remotebot"); err != nil {
t.Fatalf("delete agent: %v", err)
}
err := RecipientDeliverable(ctx, "remotebot")
if !errors.Is(err, ErrRecipientUnknown) {
t.Fatalf("删除后必须不可达,得到: %v", err)
}
}
// 停用选择「当场拒收」而不是「入库等恢复后补投」:停用的语义就是这个
// Agent 现在不干活,让发件人以为信已送达更坏 —— 它会照常等回信。
func TestRecipientDeliverable_Disabled(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "opencode", 20)
if _, err := SetAgentDisabled(ctx, "opencode", true); err != nil {
t.Fatalf("disable: %v", err)
}
err := RecipientDeliverable(ctx, "opencode")
if !errors.Is(err, ErrRecipientDisabled) {
t.Fatalf("已停用的 Agent 应当返回 ErrRecipientDisabled得到: %v", err)
}
}
// 恢复后重新可达,否则停用就成了不可逆操作。
func TestRecipientDeliverable_ReenabledAgain(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
seedAgent(t, "opencode", 20)
if _, err := SetAgentDisabled(ctx, "opencode", true); err != nil {
t.Fatalf("disable: %v", err)
}
if _, err := SetAgentDisabled(ctx, "opencode", false); err != nil {
t.Fatalf("re-enable: %v", err)
}
if err := RecipientDeliverable(ctx, "opencode"); err != nil {
t.Fatalf("恢复后应当重新可达,得到: %v", err)
}
}
// 空名交给上层的地址解析处理,这里放行 —— 不然人类给自己发信
// to 位省略 name会被这道检查误伤。
func TestRecipientDeliverable_EmptyName(t *testing.T) {
setupTestDB(t)
if err := RecipientDeliverable(context.Background(), ""); err != nil {
t.Fatalf("空名应当放行,得到: %v", err)
}
}
// 人类用户名与 Agent 名共用命名空间。一个名字同时是人类用户时按人类算 ——
// 人的收件箱一直在,不受 Agent 停用影响。
func TestRecipientDeliverable_HumanWinsOverDisabledAgent(t *testing.T) {
setupTestDB(t)
ctx := context.Background()
if _, err := db.DB.ExecContext(ctx,
`INSERT INTO users (username, password_hash) VALUES ('dual', 'x')`); err != nil {
t.Fatalf("seed user: %v", err)
}
seedAgent(t, "dual", 20)
if _, err := SetAgentDisabled(ctx, "dual", true); err != nil {
t.Fatalf("disable: %v", err)
}
if err := RecipientDeliverable(ctx, "dual"); err != nil {
t.Fatalf("同名人类用户应当优先放行,得到: %v", err)
}
}