package mcp_test import ( "context" "net/http" "net/http/httptest" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" "github.com/yan1h/agent-coding-workflow/internal/mcp" ) func TestRateLimiter_AllowUntilCap(t *testing.T) { t.Parallel() now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC) limiter := mcp.NewRateLimiter(mcp.RateLimitConfig{ PerTokenPerMinute: 3, GlobalPerMinute: 100, }, func() time.Time { return now }) for i := 0; i < 3; i++ { ok, _ := limiter.Allow("token-A") assert.True(t, ok, "request %d should pass", i) } ok, reason := limiter.Allow("token-A") assert.False(t, ok) assert.Equal(t, "per_token", reason) } func TestRateLimiter_GlobalCap(t *testing.T) { t.Parallel() now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC) limiter := mcp.NewRateLimiter(mcp.RateLimitConfig{ PerTokenPerMinute: 100, GlobalPerMinute: 2, }, func() time.Time { return now }) ok, _ := limiter.Allow("token-A") assert.True(t, ok) ok, _ = limiter.Allow("token-B") assert.True(t, ok) ok, reason := limiter.Allow("token-C") assert.False(t, ok) assert.Equal(t, "global", reason) } func TestRateLimiter_WindowReset(t *testing.T) { t.Parallel() current := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC) limiter := mcp.NewRateLimiter(mcp.RateLimitConfig{ PerTokenPerMinute: 1, GlobalPerMinute: 100, }, func() time.Time { return current }) ok, _ := limiter.Allow("A") assert.True(t, ok) ok, _ = limiter.Allow("A") assert.False(t, ok) // 跨分钟窗口 current = current.Add(time.Minute) ok, _ = limiter.Allow("A") assert.True(t, ok, "after window reset should allow again") } func TestRateLimitMiddleware_429(t *testing.T) { t.Parallel() now := time.Date(2026, 5, 7, 12, 0, 0, 0, time.UTC) limiter := mcp.NewRateLimiter(mcp.RateLimitConfig{ PerTokenPerMinute: 1, GlobalPerMinute: 100, }, func() time.Time { return now }) // 内层 handler 计数被调次数 var hits atomic.Int32 innerH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hits.Add(1) w.WriteHeader(http.StatusOK) }) // 模拟 auth 中间件已注入 ctx:直接 context.WithValue + mcp.AuthContextKey withSession := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), mcp.AuthContextKey, &mcp.AuthSession{ TokenID: "fake-token-id", UserID: "fake-user", }) mcp.NewRateLimitMiddleware(limiter)(innerH).ServeHTTP(w, r.WithContext(ctx)) }) req := httptest.NewRequest("GET", "/mcp", nil) rec := httptest.NewRecorder() withSession.ServeHTTP(rec, req) assert.Equal(t, http.StatusOK, rec.Code) rec2 := httptest.NewRecorder() withSession.ServeHTTP(rec2, req) assert.Equal(t, http.StatusTooManyRequests, rec2.Code) assert.Equal(t, int32(1), hits.Load()) }