mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-06 16:19:48 +00:00
Implements 3 coalescing layers to handle rapid multi-attachment inbounds: - Bus debouncer: delays inbound messages 1s, merges duplicates - Web chat debouncer: buffers client-side inbound frames for batch RPC - Telegram album aggregator: collects album members via AfterFunc+Stop timer Drops media-bypass shortcut (forces 1s media floor). Aggregator enforces: - AfterFunc+Stop timer discipline with ordered drain on stop - 2-tuple (album_id, sender) keying for isolation - Dual DoS caps: max 10 albums per sender, max 100 messages per album - merged_message_ids dedup seeding across all 3 surfaces Closes #63
139 lines
4.6 KiB
Go
139 lines
4.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
|
"github.com/nextlevelbuilder/goclaw/internal/config"
|
|
)
|
|
|
|
// TestApplyMediaFloor_NoMediaNoFloor: msg without media → debounceMs returned unchanged (0).
|
|
func TestApplyMediaFloor_NoMediaNoFloor(t *testing.T) {
|
|
msg := bus.InboundMessage{SenderID: "user-1"}
|
|
got := applyMediaFloor(0, msg)
|
|
if got != 0 {
|
|
t.Fatalf("applyMediaFloor(0, no-media) = %d, want 0", got)
|
|
}
|
|
}
|
|
|
|
// TestApplyMediaFloor_MediaAppliesFloorWhenDisabled — Rule #2 happy path.
|
|
func TestApplyMediaFloor_MediaAppliesFloorWhenDisabled(t *testing.T) {
|
|
msg := bus.InboundMessage{
|
|
SenderID: "user-1",
|
|
Media: []bus.MediaFile{{Path: "/x"}},
|
|
}
|
|
got := applyMediaFloor(0, msg)
|
|
if got != mediaDebounceFloorMs {
|
|
t.Fatalf("applyMediaFloor(0, media) = %d, want %d", got, mediaDebounceFloorMs)
|
|
}
|
|
}
|
|
|
|
// TestApplyMediaFloor_AgentOverrideBelowFloorHonored — red-team Rule #2 precedence.
|
|
// Floor fires only when the post-override delay is exactly 0. A 500ms override
|
|
// for a media-bearing message MUST be honored verbatim.
|
|
func TestApplyMediaFloor_AgentOverrideBelowFloorHonored(t *testing.T) {
|
|
msg := bus.InboundMessage{
|
|
SenderID: "user-1",
|
|
Media: []bus.MediaFile{{Path: "/x"}},
|
|
}
|
|
got := applyMediaFloor(500, msg)
|
|
if got != 500 {
|
|
t.Fatalf("applyMediaFloor(500, media) = %d, want 500 (override honored; floor must not raise)", got)
|
|
}
|
|
}
|
|
|
|
// TestApplyMediaFloor_MediaRespectsConfigWhenAboveFloor: cfg already above floor → unchanged.
|
|
func TestApplyMediaFloor_MediaRespectsConfigWhenAboveFloor(t *testing.T) {
|
|
msg := bus.InboundMessage{
|
|
SenderID: "user-1",
|
|
Media: []bus.MediaFile{{Path: "/x"}},
|
|
}
|
|
got := applyMediaFloor(2000, msg)
|
|
if got != 2000 {
|
|
t.Fatalf("applyMediaFloor(2000, media) = %d, want 2000", got)
|
|
}
|
|
}
|
|
|
|
// TestApplyMediaFloor_SystemSenderExempt — Rule #3 internal-publisher exemption.
|
|
func TestApplyMediaFloor_SystemSenderExempt(t *testing.T) {
|
|
msg := bus.InboundMessage{
|
|
SenderID: "system:tool-echo",
|
|
Media: []bus.MediaFile{{Path: "/x"}},
|
|
}
|
|
got := applyMediaFloor(0, msg)
|
|
if got != 0 {
|
|
t.Fatalf("applyMediaFloor(0, system:) = %d, want 0 (system: sender must be exempt from floor)", got)
|
|
}
|
|
}
|
|
|
|
// TestApplyMediaFloor_SubagentSenderExempt — Rule #3 internal-publisher exemption.
|
|
func TestApplyMediaFloor_SubagentSenderExempt(t *testing.T) {
|
|
msg := bus.InboundMessage{
|
|
SenderID: "subagent:research",
|
|
Media: []bus.MediaFile{{Path: "/x"}},
|
|
}
|
|
got := applyMediaFloor(0, msg)
|
|
if got != 0 {
|
|
t.Fatalf("applyMediaFloor(0, subagent:) = %d, want 0 (subagent: sender must be exempt from floor)", got)
|
|
}
|
|
}
|
|
|
|
// TestResolveInboundDebounceDelay_FloorIsWiredInE2E — Rule #5.
|
|
// Exercises the OUTER resolveInboundDebounceDelay function (not the helper) with
|
|
// global debounce_ms=0, AgentStore=nil, media present, normal user sender.
|
|
// Asserts the floor fires. Locks in that any helper refactor still routes through
|
|
// the public function — prevents "floor exists but isn't wired" regressions.
|
|
func TestResolveInboundDebounceDelay_FloorIsWiredInE2E(t *testing.T) {
|
|
cfg := &config.Config{}
|
|
cfg.Gateway.InboundDebounceMs = 0
|
|
deps := &ConsumerDeps{Cfg: cfg}
|
|
|
|
msg := bus.InboundMessage{
|
|
SenderID: "user-1",
|
|
AgentID: "", // skip AgentStore lookup path entirely
|
|
Media: []bus.MediaFile{{Path: "/x"}},
|
|
}
|
|
|
|
got := resolveInboundDebounceDelay(context.Background(), msg, deps)
|
|
want := time.Duration(mediaDebounceFloorMs) * time.Millisecond
|
|
if got != want {
|
|
t.Fatalf("resolveInboundDebounceDelay e2e = %v, want %v (floor not wired into outer function)", got, want)
|
|
}
|
|
}
|
|
|
|
// TestResolveInboundDebounceDelay_NoMediaNoFloorE2E: outer function, no media, returns 0.
|
|
func TestResolveInboundDebounceDelay_NoMediaNoFloorE2E(t *testing.T) {
|
|
cfg := &config.Config{}
|
|
cfg.Gateway.InboundDebounceMs = 0
|
|
deps := &ConsumerDeps{Cfg: cfg}
|
|
|
|
msg := bus.InboundMessage{SenderID: "user-1"}
|
|
got := resolveInboundDebounceDelay(context.Background(), msg, deps)
|
|
if got != 0 {
|
|
t.Fatalf("resolveInboundDebounceDelay no-media = %v, want 0", got)
|
|
}
|
|
}
|
|
|
|
// TestIsSystemOrSubagentSender_PrefixMatch covers helper directly.
|
|
func TestIsSystemOrSubagentSender_PrefixMatch(t *testing.T) {
|
|
cases := []struct {
|
|
senderID string
|
|
want bool
|
|
}{
|
|
{"system:escalation", true},
|
|
{"system:tool:sessions_send", true},
|
|
{"subagent:research", true},
|
|
{"user-1", false},
|
|
{"", false},
|
|
{"systemic", false}, // not the colon-prefix form
|
|
{"subagentX", false}, // not the colon-prefix form
|
|
}
|
|
for _, c := range cases {
|
|
if got := isSystemOrSubagentSender(c.senderID); got != c.want {
|
|
t.Errorf("isSystemOrSubagentSender(%q) = %v, want %v", c.senderID, got, c.want)
|
|
}
|
|
}
|
|
}
|