Files
MailUI4Agents/server/internal/repo/agent_mode_list_test.go
JianFeeeee 0b5fabfd49 fix(repo): /api/v1/agents 带出 mode_enforcement —— 列表接口静默少了一个事实
# 现象

`/api/v1/agents` 对全部四个 Agent 返回 `mode_enforcement: ""`,
而库里四个值一直是正确的(pi=native / dsh=partial / opencode=advisory /
homeagent=advisory)。这是全功能演练 Phase A 的前置检查抓到的。

# 根因

`ListAgents` 的 SELECT 里没有 `mode_enforcement`,Scan 自然也没扫它。

这与「SELECT 加了列但没加进 Scan」(列数不匹配,直接报错)**不是**一回事:
少取一列不报任何错,只是安静地少一个事实。而「这个 Agent 到底能不能真的拦住
危险操作」是使用者在派活前必须知道的事 —— 缺了它,advisory 档会被当成 native
档用。

# 修法

SELECT 补上 `COALESCE(NULLIF(mode_enforcement, ''), 'advisory')`,与
`permission_mode.go` 同一套兜底(那里也这么写,说明空串确实可能出现)。

# 测试

`repo/agent_mode_list_test.go`:三档各一条 + 一条空串(脏数据),断言**值真的
传出来**而不是「函数没报错」;另加反向对照确认行真的被查到了(避免一个都没查到
也能过)。

反向验证过判据非空转:把修复退掉,测试立刻失败。
2026-09-12 11:19:52 +08:00

68 lines
2.4 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"
"github.com/agentmail/gateway/internal/db"
)
// ListAgents 必须把 mode_enforcement 带出来。
//
// # 为什么需要这条测试
//
// 该字段曾是**静默丢失**的:`ListAgents` 的 SELECT 里没有它Scan 自然也没扫,
// 于是 `/api/v1/agents` 对全部 Agent 返回空串 —— 而库里四个 Agent 的值
// native / partial / advisory一直是对的。
//
// 这类故障跟「SELECT 加列但没加进 Scan」列数不匹配直接报错**不是**一回事:
// 少取一列不会报任何错,只是安静地少一个事实。而「这个 Agent 到底能不能真的
// 拦住危险操作」是使用者在派活前必须知道的事缺了它advisory 档会被当成
// native 档用 —— 正是本项目一直在治的那种「静默不一致」。
//
// 所以判据是**值真的传出来**,不是「函数没报错」。
func TestListAgentsCarriesModeEnforcement(t *testing.T) {
ctx := context.Background()
setupTestDB(t)
database := db.DB
// 三轮覆盖三档并包含「空串」这种脏数据permission_mode.go 用
// NULLIF(...,'') 兜底,说明空串确实可能出现)。
cases := []struct{ name, mode, want string }{
{"t-mode-native", "native", "native"},
{"t-mode-partial", "partial", "partial"},
{"t-mode-advisory", "advisory", "advisory"},
{"t-mode-empty", "", "advisory"},
}
for _, c := range cases {
if _, err := database.ExecContext(ctx,
`INSERT INTO agents (agent_name, secret, platform, status, mode_enforcement)
VALUES ($1, 's', 'pi', 'online', $2)`, c.name, c.mode); err != nil {
t.Fatalf("插入 %s 失败: %v", c.name, err)
}
}
agents, err := ListAgents(ctx, "all")
if err != nil {
t.Fatalf("ListAgents 出错: %v", err)
}
got := map[string]string{}
for _, a := range agents {
got[a.Name] = a.ModeEnforcement
}
for _, c := range cases {
if got[c.name] != c.want {
t.Errorf("%smode_enforcement 传出来是 %q应为 %q —— "+
"列表接口必须带出档位,否则调用方会把 advisory 当 native 用",
c.name, got[c.name], c.want)
}
}
// 反向对照:确认这些行确实被查到了(避免「一个都没查到」也能过)。
if len(agents) < len(cases) {
t.Errorf("只查到 %d 行,期望至少 %d 行 —— 上面的断言可能是空转",
len(agents), len(cases))
}
}