pi 2026-09-14 三条(他接受了我对"恒红=相位错"的反驳,但指出 Skip 带来的两处漏洞)。
1. **Skip 必须进余额、条件必须是测量**:
- **条件**:跳过与否由 `measureMailStatusDebt` **实测**(详情路径的 status 是否真的
等于按读者派生),不是常量、不是"我们还没迁完"这种没人会更新的事实;
- **余额**:`docs/DEBTS.json` 是**唯一登记**,Go 侧判据 `TestDebtLedgerMatchesMeasurement`
**自己测量**后与登记比对(第一版我让余额由另一条判据写入 ⇒ **排序依赖**,
Go 同包内按源文件顺序跑,登记那条先跑就读到 0 —— 排序依赖是隐蔽的假绿,已抽成自足函数);
- **可见性**:`go test` 跑通时**不打印包的输出**,我第一版把余额打在 TestMain 里,
常态运行一个字都看不见 —— 正是 pi 说的"不显形"。所以常态可见的那份打在
electron 套件的 RESULT 行:`RESULT phase=install static=5 debts=7
(static-criteria:5,mails-status-derived:1,gesture-semantics:1) probe=ok`。
2. **豁免从"按文件"改成"按文件 + 次数"**:`migrate.go` 这类比较**上限 2 处**(附理由),
多一处即红。我在读侧清册上自己修过这个洞,豁免那格却退了一格 —— pi 指出得对。
3. **三笔欠账合成一处**:原先各自表达(`RESULT static=5` / `t.Skip` 无余额 /
文档里的到期前提无余额),**没有一处能一眼看全**。现在统一登记在 `docs/DEBTS.json`
(id / 余额 / 到期前提 / 判据位置),两端读同一份:Go 侧比对实测,electron 侧打进 RESULT 行。
还清那天:登记要跟着清 —— 不清则由 `TestDebtLedgerMatchesMeasurement` 报
"**欠账已还清**,但登记还记着 N"(还清是可测事件,这正是那条判据存在的意义)。
131 lines
3.7 KiB
Go
131 lines
3.7 KiB
Go
package repo
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"testing"
|
||
|
||
"github.com/agentmail/gateway/internal/db"
|
||
)
|
||
|
||
// setupTestDB 起一个临时 SQLite 库并建表,供配额测试使用。
|
||
// 直接用真实的 SQLite 而非 mock:配额的正确性核心在于「判断与自增在同一条 UPDATE 里」,
|
||
// 这正是只有真实数据库才能验证的部分。
|
||
func setupTestDB(t *testing.T) {
|
||
t.Helper()
|
||
|
||
dir := t.TempDir()
|
||
if err := db.Connect(context.Background(), filepath.Join(dir, "test.db")); err != nil {
|
||
t.Fatalf("connect: %v", err)
|
||
}
|
||
if err := db.Migrate(context.Background()); err != nil {
|
||
t.Fatalf("migrate: %v", err)
|
||
}
|
||
t.Cleanup(db.Close)
|
||
}
|
||
|
||
func seedAgent(t *testing.T, name string, max int) {
|
||
t.Helper()
|
||
_, err := db.DB.ExecContext(context.Background(),
|
||
`INSERT INTO agents (agent_name, secret, platform, default_rounds) VALUES ($1, 'x', 'test', $2)`,
|
||
name, max)
|
||
if err != nil {
|
||
t.Fatalf("seed agent: %v", err)
|
||
}
|
||
}
|
||
|
||
// default_rounds 是「派给这个 Agent 的新任务默认多少个来回」,
|
||
// 不是会拦请求的终身额度 —— 真正的额度在 sessions.max_rounds 上。
|
||
func TestDefaultRoundsRoundTrip(t *testing.T) {
|
||
setupTestDB(t)
|
||
seedAgent(t, "bot", 0)
|
||
ctx := context.Background()
|
||
|
||
st, err := SetDefaultRounds(ctx, "bot", 15)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if st.DefaultRounds != 15 {
|
||
t.Fatalf("default_rounds = %d,期望 15", st.DefaultRounds)
|
||
}
|
||
if got := DefaultRoundsFor(ctx, "bot"); got != 15 {
|
||
t.Fatalf("DefaultRoundsFor = %d,期望 15", got)
|
||
}
|
||
|
||
// 负数归一为 0(不限),而不是造出一个永远发不出信的默认值
|
||
if st, err = SetDefaultRounds(ctx, "bot", -3); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if st.DefaultRounds != 0 {
|
||
t.Fatalf("负数应归一为 0,实际 %d", st.DefaultRounds)
|
||
}
|
||
}
|
||
|
||
// 未注册的 Agent 取默认预算时给兜底值而不是报错:
|
||
// 派活的人不该因为「对方还没上线」就拿不到一个合理默认值 ——
|
||
// 邮件本来就支持发给尚未上线的收件人。
|
||
func TestDefaultRoundsForUnknownAgentFallsBack(t *testing.T) {
|
||
setupTestDB(t)
|
||
if got := DefaultRoundsFor(context.Background(), "ghost"); got != fallbackDefaultRounds {
|
||
t.Fatalf("未注册 Agent 应回落到 %d,实际 %d", fallbackDefaultRounds, got)
|
||
}
|
||
}
|
||
|
||
// BumpSentCount 是纯统计:只累加,绝不拦请求,也绝不返回错误
|
||
func TestBumpSentCountOnlyCounts(t *testing.T) {
|
||
setupTestDB(t)
|
||
seedAgent(t, "bot", 0)
|
||
ctx := context.Background()
|
||
|
||
for i := 0; i < 5; i++ {
|
||
BumpSentCount(ctx, "bot")
|
||
}
|
||
st, err := GetAgentStats(ctx, "bot")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if st.SentTotal != 5 {
|
||
t.Fatalf("累计发信 = %d,期望 5", st.SentTotal)
|
||
}
|
||
|
||
// 不存在的 Agent 也不该 panic 或报错 —— 它只是没有行可更新
|
||
BumpSentCount(ctx, "ghost")
|
||
}
|
||
|
||
func TestGetAgentStatsUnknownAgent(t *testing.T) {
|
||
setupTestDB(t)
|
||
if _, err := GetAgentStats(context.Background(), "ghost"); err == nil {
|
||
t.Fatal("不存在的 Agent 应报错")
|
||
}
|
||
}
|
||
|
||
func TestListAgentStats(t *testing.T) {
|
||
setupTestDB(t)
|
||
seedAgent(t, "alpha", 0)
|
||
seedAgent(t, "beta", 0)
|
||
ctx := context.Background()
|
||
SetDefaultRounds(ctx, "alpha", 5)
|
||
|
||
list, err := ListAgentStats(ctx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(list) != 2 {
|
||
t.Fatalf("应有 2 个 Agent,实际 %d", len(list))
|
||
}
|
||
// 按名字排序,alpha 在前
|
||
if list[0].AgentName != "alpha" || list[0].DefaultRounds != 5 {
|
||
t.Fatalf("alpha 的记录不对:%+v", list[0])
|
||
}
|
||
}
|
||
|
||
func TestMain(m *testing.M) {
|
||
code := m.Run()
|
||
// 欠账余额打在收尾:Skip 在 `go test` 里只是退出码 0 的一行输出,
|
||
// 不进余额就等于"不显形"(pi 2026-09-14 裁定 §1)。
|
||
fmt.Println(debtSummary())
|
||
os.Exit(code)
|
||
}
|