Files
MailUI4Agents/server/internal/repo/quota_test.go
JianFeeeee d25770ea2f fix(欠账): Skip 进余额且条件必须是测量;豁免按文件+次数;三笔欠账合成一处可读余额
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"(还清是可测事件,这正是那条判据存在的意义)。
2026-09-14 17:17:37 +08:00

131 lines
3.7 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"
"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)
}