- Rewrite chat rate limiter with Redis sliding window (sorted sets) and automatic in-memory fallback when Redis is unavailable - Add ChatPresenceService with Redis-backed online/offline/heartbeat tracking (2min TTL), integrated into Hub register/unregister - Add migration 113: tsvector column with GIN index and auto-update trigger on messages table for full-text search - Update Search repository method to use ts_rank ordering instead of ILIKE - Wire Redis client into chat WebSocket setup in router.go - Add comprehensive tests: rate limiter, presence, 100-user concurrent benchmark
75 lines
2 KiB
Go
75 lines
2 KiB
Go
package chat
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"go.uber.org/zap/zaptest"
|
|
)
|
|
|
|
func TestRateLimiter_InMemoryFallback_AllowsWithinLimit(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
rl := NewRateLimiter(nil, logger)
|
|
|
|
userID := uuid.New()
|
|
for i := 0; i < 10; i++ {
|
|
assert.True(t, rl.Allow(userID, "send_message"), "request %d should be allowed", i+1)
|
|
}
|
|
assert.False(t, rl.Allow(userID, "send_message"), "11th request should be denied")
|
|
}
|
|
|
|
func TestRateLimiter_InMemoryFallback_WindowReset(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
rl := NewRateLimiter(nil, logger)
|
|
|
|
rl.limits["test_action"] = rateConfig{maxRequests: 2, window: 50 * time.Millisecond}
|
|
|
|
userID := uuid.New()
|
|
assert.True(t, rl.Allow(userID, "test_action"))
|
|
assert.True(t, rl.Allow(userID, "test_action"))
|
|
assert.False(t, rl.Allow(userID, "test_action"))
|
|
|
|
time.Sleep(60 * time.Millisecond)
|
|
assert.True(t, rl.Allow(userID, "test_action"), "should allow after window reset")
|
|
}
|
|
|
|
func TestRateLimiter_UnknownAction_AlwaysAllowed(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
rl := NewRateLimiter(nil, logger)
|
|
|
|
userID := uuid.New()
|
|
for i := 0; i < 100; i++ {
|
|
assert.True(t, rl.Allow(userID, "unknown_action"))
|
|
}
|
|
}
|
|
|
|
func TestRateLimiter_DifferentUsers_IndependentLimits(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
rl := NewRateLimiter(nil, logger)
|
|
|
|
user1 := uuid.New()
|
|
user2 := uuid.New()
|
|
|
|
for i := 0; i < 10; i++ {
|
|
assert.True(t, rl.Allow(user1, "send_message"))
|
|
}
|
|
assert.False(t, rl.Allow(user1, "send_message"))
|
|
|
|
assert.True(t, rl.Allow(user2, "send_message"), "user2 should be independent of user1")
|
|
}
|
|
|
|
func TestRateLimiter_DifferentActions_IndependentLimits(t *testing.T) {
|
|
logger := zaptest.NewLogger(t)
|
|
rl := NewRateLimiter(nil, logger)
|
|
|
|
userID := uuid.New()
|
|
|
|
for i := 0; i < 10; i++ {
|
|
assert.True(t, rl.Allow(userID, "send_message"))
|
|
}
|
|
assert.False(t, rl.Allow(userID, "send_message"))
|
|
|
|
assert.True(t, rl.Allow(userID, "typing"), "typing should be independent of send_message")
|
|
}
|