Files
Cyrene_v2/internal/channel/session/proactive_test.go
T

172 lines
4.6 KiB
Go

package session
import (
"context"
"testing"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
)
func capsProactive() adapter.Capabilities {
return adapter.Capabilities{Proactive: true}
}
func capsNoProactive() adapter.Capabilities {
return adapter.Capabilities{Proactive: false}
}
func TestProactivePolicyCapabilityGate(t *testing.T) {
t.Parallel()
p := NewProactivePolicy(ProactiveOptions{})
// A channel that does not declare proactive support is denied regardless of
// how fresh the last message is.
if p.Allow(context.Background(), capsNoProactive(), time.Now()) {
t.Error("Allow() = true with Proactive=false, want false")
}
}
func TestProactivePolicyWindowWithin(t *testing.T) {
t.Parallel()
p := NewProactivePolicy(ProactiveOptions{Window: time.Hour})
last := time.Now().Add(-30 * time.Minute)
if !p.Allow(context.Background(), capsProactive(), last) {
t.Error("Allow() = false within window, want true")
}
}
func TestProactivePolicyWindowBeyond(t *testing.T) {
t.Parallel()
p := NewProactivePolicy(ProactiveOptions{Window: time.Hour})
last := time.Now().Add(-90 * time.Minute)
if p.Allow(context.Background(), capsProactive(), last) {
t.Error("Allow() = true beyond window, want false")
}
}
func TestProactivePolicyWindowZeroLastMessage(t *testing.T) {
t.Parallel()
p := NewProactivePolicy(ProactiveOptions{Window: time.Hour})
if p.Allow(context.Background(), capsProactive(), time.Time{}) {
t.Error("Allow() = true with no last message, want false")
}
}
func TestProactivePolicyRateLimit(t *testing.T) {
t.Parallel()
p := NewProactivePolicy(ProactiveOptions{RateLimit: 3, RateWindow: 10 * time.Second})
ctx := context.Background()
for i := 1; i <= 3; i++ {
if !p.Allow(ctx, capsProactive(), time.Now()) {
t.Fatalf("Allow() denied on attempt %d, want allowed under limit", i)
}
}
if p.Allow(ctx, capsProactive(), time.Now()) {
t.Error("Allow() allowed past burst limit, want denied")
}
}
func TestProactivePolicyUnrestrictedChannel(t *testing.T) {
t.Parallel()
// telegram/webhook-like channel: proactive capability on, no window, no
// rate limit → always allowed.
p := NewProactivePolicy(ProactiveOptions{})
old := time.Now().Add(-48 * time.Hour)
for i := 0; i < 5; i++ {
if !p.Allow(context.Background(), capsProactive(), old) {
t.Fatalf("Allow() denied on unrestricted channel, want true")
}
}
}
func TestProactivePolicyForPlatform(t *testing.T) {
t.Parallel()
ctx := context.Background()
iLink := ProactivePolicyForPlatform("weixin-ilink")
now := time.Now()
if !iLink.Allow(ctx, capsProactive(), now.Add(-23*time.Hour)) {
t.Error("weixin-ilink allowed only within 24h; 23h old should allow")
}
if iLink.Allow(ctx, capsProactive(), now.Add(-25*time.Hour)) {
t.Error("weixin-ilink 25h old last message should deny")
}
oneBot := ProactivePolicyForPlatform("qq-obv11")
for i := 0; i < oneBotBurstLimit; i++ {
if !oneBot.Allow(ctx, capsProactive(), now) {
t.Fatalf("onebot denied on attempt %d, want allowed", i+1)
}
}
if oneBot.Allow(ctx, capsProactive(), now) {
t.Error("onebot allowed past burst limit, want denied")
}
tg := ProactivePolicyForPlatform("telegram")
if !tg.Allow(ctx, capsProactive(), now.Add(-72*time.Hour)) {
t.Error("telegram must push freely, want true")
}
}
func TestProactivePolicyNilPolicyAndCanceledCtx(t *testing.T) {
t.Parallel()
var p *ProactivePolicy
if p.Allow(context.Background(), capsProactive(), time.Now()) {
t.Error("nil policy Allow() = true, want false (fail closed)")
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
policy := NewProactivePolicy(ProactiveOptions{})
if policy.Allow(ctx, capsProactive(), time.Now()) {
t.Error("Allow() with canceled ctx = true, want false")
}
}
func TestRateLimiterSlidingWindow(t *testing.T) {
t.Parallel()
l := NewRateLimiter(2, 50*time.Millisecond)
if !l.Allow() {
t.Fatal("first Allow() = false, want true")
}
if !l.Allow() {
t.Fatal("second Allow() = false, want true")
}
if l.Allow() {
t.Fatal("third Allow() = true, want false (limit reached)")
}
time.Sleep(60 * time.Millisecond)
if !l.Allow() {
t.Fatal("Allow() after window elapsed = false, want true")
}
}
func TestRateLimiterDisabledConfig(t *testing.T) {
t.Parallel()
// A limiter with no limit/window configured is the safe permissive default.
configs := []*RateLimiter{
NewRateLimiter(0, 0),
NewRateLimiter(-1, time.Second),
NewRateLimiter(0, time.Second),
}
for i, l := range configs {
if !l.Allow() || !l.Allow() {
t.Errorf("config %d: Allow() = false, want true (unlimited)", i)
}
}
if !(*RateLimiter)(nil).Allow() {
t.Error("nil rate limiter Allow() = false, want true (permissive)")
}
}