Files
MailUI4Agents/plugins/homeagent-mail-bridge/bounded_test.go
JianFeeeee a44fd6949b feat: 权限档位体系(三档 plan/workspace/full + 四桥 from_session_id)
L2 核心改动:sessions 表补 permission_mode / permission_enforcement 两列
(sqlite + pg 同步),三桥 lib/permission-mode.js 翻译档位到平台原生配置,
homeagent advisory 模式提示词告知模型实际强制力。四桥全部携带 from_session_id
供 relay 去重与会话回溯。

FromHuman / ToHuman 判据已加入心跳 payload 与 notify/mail.go。
2026-09-06 15:16:49 +08:00

163 lines
5.0 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 main
import (
"fmt"
"testing"
)
// 这些用例钉住的是 bounded.go 的三条性质封顶、FIFO、以及
// 「add 的返回值就是查重结果」—— 后者让调用方能在一把锁里查重 + 登记。
func TestBoundedSetCapsSize(t *testing.T) {
s := newBoundedIDSet(3)
for _, id := range []string{"a", "b", "c", "d", "e"} {
s.add(id)
}
if s.size() != 3 {
t.Fatalf("上限之后 size 必须封顶,得到 %d —— 这正是泄露的反面", s.size())
}
if s.has("a") || s.has("b") {
t.Error("最早插入的两条应当被淘汰")
}
for _, id := range []string{"c", "d", "e"} {
if !s.has(id) {
t.Errorf("%s 应当还在", id)
}
}
if s.evicted != 2 {
t.Errorf("evicted 应为 2得到 %d", s.evicted)
}
}
func TestBoundedSetAddReportsFreshness(t *testing.T) {
// 调用方plugin.go 的两处去重)依赖这个返回值在同一把锁里完成
// 「查重 + 登记」。分两步做需要两次加锁,中间那个窗口正是原来的竞态。
s := newBoundedIDSet(10)
if !s.add("m1") {
t.Error("首次 add 应当返回 true新加入")
}
if s.add("m1") {
t.Error("重复 add 应当返回 false已存在")
}
if s.size() != 1 {
t.Errorf("重复 add 不该占额外位置size=%d", s.size())
}
}
func TestBoundedSetFIFONotLRU(t *testing.T) {
// Go 的 map 不保证遍历顺序,所以这里是显式的 FIFO 而不是 LRU。
// 这条用例把那个决定钉住:反复 has 不会让条目留得更久。
s := newBoundedIDSet(2)
s.add("a")
s.add("b")
for i := 0; i < 5; i++ {
s.has("a") // 在 LRU 语义下这会保住 a
}
s.add("c")
if s.has("a") {
t.Error("FIFO 语义下最早插入的 a 应当被淘汰has() 不刷新活跃度")
}
if !s.has("b") || !s.has("c") {
t.Error("b 与 c 应当都在")
}
}
func TestBoundedSetReAddDoesNotRefreshPosition(t *testing.T) {
// 重复 add 也不改变位置FIFO 由首次插入决定)。写成「已存在时移到队尾」
// 会让一条被反复推送的邮件把别的条目挤出去。
s := newBoundedIDSet(2)
s.add("a")
s.add("b")
s.add("a") // 已存在,位置不变
s.add("c")
if s.has("a") {
t.Error("a 是最早插入的,重复 add 不该救回它")
}
}
func TestBoundedSetIgnoresEmptyID(t *testing.T) {
// 空 mail_id 是「事件残缺」而不是「一封 id 为空的邮件」。
// 记住它会让第二封残缺事件被误判成重复。
s := newBoundedIDSet(5)
if s.add("") {
t.Error("空 id 不该被记住")
}
if s.has("") {
t.Error("空 id 永远不算已见过")
}
if s.size() != 0 {
t.Errorf("空 id 不该占位置size=%d", s.size())
}
}
func TestBoundedSetNilSafe(t *testing.T) {
// 构造函数失败或字段没初始化时不该 panic —— 去重是优化项,
// 退化成「什么都不记得」(多几次重复投递)远好过插件崩溃。
var s *boundedIDSet
if s.has("x") {
t.Error("nil 上 has 应为 false")
}
if s.add("x") {
t.Error("nil 上 add 应为 false")
}
if s.size() != 0 {
t.Error("nil 上 size 应为 0")
}
}
func TestBoundedSetIllegalLimitFallsBackToOne(t *testing.T) {
// 上限非法时回落到 1 而不是 panic 或 0。
// 0 的后果最隐蔽:每次 add 之后立刻把自己淘汰掉 → 去重全失效且不报错。
for _, limit := range []int{0, -1, -100} {
s := newBoundedIDSet(limit)
s.add("a")
if !s.has("a") {
t.Errorf("limit=%d刚加入的那条必须还在回落到 1而不是 0", limit)
}
s.add("b")
if s.size() != 1 {
t.Errorf("limit=%dsize 应为 1得到 %d", limit, s.size())
}
}
}
func TestBoundedSetCompactsOrderSlice(t *testing.T) {
// order 切片的底层数组不能随插入次数无限增长 —— 那正是这张表要修的病,
// 只是换了个地方map 有界了,切片没有)。
s := newBoundedIDSet(10)
for i := 0; i < 10_000; i++ {
s.add(fmt.Sprintf("mail-%d", i))
}
if s.size() != 10 {
t.Fatalf("size 应当封顶在 10得到 %d", s.size())
}
// 压实之后 order 的有效长度不该远大于上限
if live := len(s.order) - s.head; live > 10 {
t.Errorf("order 有效长度 %d 超过上限 10", live)
}
if len(s.order) > 10*4 {
t.Errorf("order 底层长度 %d 相对上限 10 增长失控(压实没生效)", len(s.order))
}
if cap(s.order) > 10*8 {
t.Errorf("order 容量 %d 相对上限 10 增长失控", cap(s.order))
}
// 最新的必须还在,最老的必须没了
if !s.has("mail-9999") {
t.Error("最新的那条必须还在")
}
if s.has("mail-0") {
t.Error("最老的那条必须已被淘汰")
}
if s.evicted != 9990 {
t.Errorf("evicted 应为 9990得到 %d", s.evicted)
}
}
func TestBoundedSetLimitMatchesNodeSide(t *testing.T) {
// 与 Node 侧 lib/bounded.js 的 MAX_TRACKED_MAILS 取同一个数。
// 四个平台在同一套语义下运行,一侧偷偷调小会让「重复投递」只在那个平台出现。
if maxTrackedMails != 2000 {
t.Errorf("maxTrackedMails 应为 2000与 Node 侧一致),得到 %d", maxTrackedMails)
}
}