Files
goclaw/cmd/gateway_consumer_debounce_test.go
Duy /zuey/andGitHub f771cff77c fix(channels): coalesce multi-attachment inbounds (#63) (#90)
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
2026-05-28 18:30:34 +07:00

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)
}
}
}