Files
zeekay c6dc8ac2f7 Hanzo Team
The Go transactor and account service for Hanzo Team.

History starts here. The previous 107 commits carried the fork's origin in
their messages and their tree, and none of it described this code: the Go
under pkg/transactor is Hanzo's own work implementing a wire protocol, not
a port of anyone's implementation.

pkg/transactor/model.json remains, and remains attributed. It is a compiled
platform model this transactor loads through go:embed, governed by EPL-2.0
and named in NOTICE. Removing it is a replacement project -- a new compiler
and a move off the protocol -- not a deletion.
2026-08-30 17:07:38 -07:00

106 lines
2.6 KiB
Go

package slack
import (
"sync"
"testing"
"time"
)
func TestSeenSet_FirstThenDuplicate(t *testing.T) {
s := newSeenSet(time.Minute)
now := time.Unix(1_700_000_000, 0)
if s.seenAndAdd("ev1", now) {
t.Fatal("first sighting reported as duplicate")
}
if !s.seenAndAdd("ev1", now) {
t.Fatal("second sighting not caught as duplicate")
}
}
func TestSeenSet_EmptyKeyNeverBlocks(t *testing.T) {
s := newSeenSet(time.Minute)
now := time.Unix(1_700_000_000, 0)
if s.seenAndAdd("", now) || s.seenAndAdd("", now) {
t.Fatal("empty key must be non-dedupable (never blocks)")
}
}
func TestSeenSet_ExpiryAllowsReuse(t *testing.T) {
s := newSeenSet(time.Minute)
base := time.Unix(1_700_000_000, 0)
if s.seenAndAdd("ev", base) {
t.Fatal("first add flagged duplicate")
}
// Within TTL → still a duplicate.
if !s.seenAndAdd("ev", base.Add(30*time.Second)) {
t.Fatal("within-TTL replay not caught")
}
// After TTL → the entry has aged out, so it's a fresh sighting again.
if s.seenAndAdd("ev", base.Add(2*time.Minute)) {
t.Fatal("entry did not expire after TTL")
}
}
// TestSeenSet_EvictThenReplayResistance: a flood of fresh keys must NOT evict a
// still-fresh target key (age-based, not count-based). This is the property that
// closes the evict-then-replay attack.
func TestSeenSet_NoEarlyEvictionUnderFlood(t *testing.T) {
s := newSeenSet(time.Minute)
base := time.Unix(1_700_000_000, 0)
// Record the target.
s.seenAndAdd("target", base)
// Flood with 10k fresh keys, all within the TTL window.
for i := 0; i < 10000; i++ {
s.seenAndAdd("flood-"+string(rune('a'+i%26))+itoa(i), base.Add(time.Duration(i)*time.Millisecond))
}
// The target is still fresh (well within 1 min) → must still be seen.
if !s.seenAndAdd("target", base.Add(2*time.Second)) {
t.Fatal("fresh target was evicted by flood (evict-then-replay window open)")
}
}
func TestSeenSet_ConcurrentTestAndSet(t *testing.T) {
// Two goroutines racing on the SAME key: exactly one must see false (the
// winner), the rest true. Proves the test-and-set is atomic.
s := newSeenSet(time.Minute)
now := time.Now()
const n = 64
var wg sync.WaitGroup
var mu sync.Mutex
firsts := 0
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if !s.seenAndAdd("k", now) {
mu.Lock()
firsts++
mu.Unlock()
}
}()
}
wg.Wait()
if firsts != 1 {
t.Fatalf("test-and-set not atomic: %d goroutines saw first (want 1)", firsts)
}
}
func itoa(i int) string {
if i == 0 {
return "0"
}
var b []byte
neg := i < 0
if neg {
i = -i
}
for i > 0 {
b = append([]byte{byte('0' + i%10)}, b...)
i /= 10
}
if neg {
b = append([]byte{'-'}, b...)
}
return string(b)
}