chore: directory migration - gateway→server, web→client/electron

This commit is contained in:
2026-09-08 19:16:35 +08:00
parent fd9f99a3f9
commit f9d757b5e5
243 changed files with 5095 additions and 228 deletions

View File

@ -0,0 +1,135 @@
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)
}
}