Files
goclaw/cmd/gateway_consumer_dedup_test.go
Duy /zuey/ 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

68 lines
2.1 KiB
Go

package cmd
import (
"testing"
"time"
"github.com/nextlevelbuilder/goclaw/internal/bus"
)
// TestConsumerDedupSeedsAlbumSiblings — Rule #4 (Phase 1).
// When the debouncer flushes a merged message, all sibling message_ids
// (from metadata["merged_message_ids"]) MUST be seeded into the dedup cache
// so any platform retransmit of a sibling member is short-circuited.
func TestConsumerDedupSeedsAlbumSiblings(t *testing.T) {
dedupe := bus.NewDedupeCache(20*time.Minute, 5000)
merged := bus.InboundMessage{
Channel: "telegram",
ChatID: "chat-1",
SenderID: "user-1",
Metadata: map[string]string{
"message_id": "m3",
"merged_message_ids": "m1,m2,m3",
},
}
seedDedupFromMerged(dedupe, merged)
// Sibling retransmits MUST be reported as duplicates after seeding.
for _, mid := range []string{"m1", "m2", "m3"} {
key := dedupKeyFor("telegram", "user-1", "chat-1", mid)
if !dedupe.IsDuplicate(key) {
t.Fatalf("sibling %q not seeded; key=%q", mid, key)
}
}
// Unrelated message_id must NOT be marked duplicate.
otherKey := dedupKeyFor("telegram", "user-1", "chat-1", "m99")
if dedupe.IsDuplicate(otherKey) {
t.Fatalf("unrelated message m99 wrongly marked duplicate")
}
}
// TestConsumerDedupSeedsNoopWhenMergedEmpty: no merged_message_ids → no-op.
func TestConsumerDedupSeedsNoopWhenMergedEmpty(t *testing.T) {
dedupe := bus.NewDedupeCache(20*time.Minute, 5000)
msg := bus.InboundMessage{
Channel: "telegram",
ChatID: "chat-1",
SenderID: "user-1",
Metadata: map[string]string{"message_id": "m1"},
}
seedDedupFromMerged(dedupe, msg) // must not panic
// m1 should NOT have been seeded by this helper (only the merged-list path seeds).
key := dedupKeyFor("telegram", "user-1", "chat-1", "m1")
if dedupe.IsDuplicate(key) {
t.Fatalf("m1 wrongly seeded for non-merged message")
}
}
// TestConsumerDedupSeedsHandlesNilMetadata: nil metadata → no-op, no panic.
func TestConsumerDedupSeedsHandlesNilMetadata(t *testing.T) {
dedupe := bus.NewDedupeCache(20*time.Minute, 5000)
msg := bus.InboundMessage{Channel: "telegram", ChatID: "c", SenderID: "u"}
seedDedupFromMerged(dedupe, msg) // must not panic
}