136 lines
4.2 KiB
Go
136 lines
4.2 KiB
Go
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)
|
||
}
|
||
}
|