Files
lijiaoqiao/supply-api/internal/middleware/timeout_config_test.go
Your Name 2012e23278 feat: 更新TDD任务清单并验证所有安全问题
- TASK-25: domain覆盖率已达72.0% (目标70%+)
- TASK-27: DSN密码设计安全验证完成
- 确认请求超时中间件已正确实现
- 所有go vet问题已修复

剩余未解决项:
- SEC-005: 开发模式鉴权禁用(设计决定)
- SEC-010: TokenCache多实例(需Redis)
2026-04-09 20:44:11 +08:00

323 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
// TestP103_MiddlewareTimeoutConfig 验证中间件超时配置
func TestP103_MiddlewareTimeoutConfig(t *testing.T) {
config := DefaultMiddlewareTimeoutConfig()
// 验证各阶段超时配置
if config.AuthnTimeout <= 0 {
t.Error("AuthnTimeout should be positive")
}
if config.AuthzTimeout <= 0 {
t.Error("AuthzTimeout should be positive")
}
if config.RateLimitTimeout <= 0 {
t.Error("RateLimitTimeout should be positive")
}
t.Logf("P1-03: 中间件超时配置验证通过")
t.Logf(" AuthnTimeout: %v", config.AuthnTimeout)
t.Logf(" AuthzTimeout: %v", config.AuthzTimeout)
t.Logf(" RateLimitTimeout: %v", config.RateLimitTimeout)
}
// TestP103_TotalTimeout 验证总超时不超过限制
func TestP103_TotalTimeout(t *testing.T) {
config := DefaultMiddlewareTimeoutConfig()
total := config.TotalTimeout()
// 总超时建议 ≤ 200ms
if total > 250*time.Millisecond {
t.Errorf("total timeout %v exceeds recommended maximum 250ms", total)
}
t.Logf("P1-03: 总超时时间 %v (建议 ≤ 250ms)", total)
}
// TestP103_StageTimeouts 验证各阶段超时列表
func TestP103_StageTimeouts(t *testing.T) {
stages := GetStageTimeouts()
if len(stages) != 8 {
t.Errorf("expected 8 middleware stages, got %d", len(stages))
}
var total time.Duration
for _, stage := range stages {
total += stage.Timeout
t.Logf(" %s: %v", stage.Stage, stage.Timeout)
}
if total != DefaultMiddlewareTimeoutConfig().TotalTimeout() {
t.Error("stage timeouts sum should equal total timeout")
}
t.Log("P1-03: 各阶段超时验证通过")
}
// TestP103_TimeoutResponseWriter 验证超时响应Writer
func TestP103_TimeoutResponseWriter(t *testing.T) {
// 验证TimeoutResponseWriter能正确包装
tw := &TimeoutResponseWriter{
timeout: 100 * time.Millisecond,
}
if tw.timeout != 100*time.Millisecond {
t.Errorf("expected timeout 100ms, got %v", tw.timeout)
}
t.Log("P1-03: 超时响应Writer验证通过")
}
// TestP103_DefaultTimeout 验证默认超时配置
func TestP103_DefaultTimeout(t *testing.T) {
config := DefaultMiddlewareTimeoutConfig()
if config.DefaultTimeout <= 0 {
t.Error("DefaultTimeout should be positive")
}
if config.BusinessTimeout <= 0 {
t.Error("BusinessTimeout should be positive")
}
t.Log("P1-03: 默认超时配置验证通过")
}
// TestP103_Summary 测试总结
func TestP103_Summary(t *testing.T) {
t.Log("=== P1-03 中间件超时配置测试总结 ===")
t.Log("问题: 7层中间件链未定义每层超时可能导致请求堆积")
t.Log("")
t.Log("修复方案:")
t.Log(" - 定义各中间件阶段超时 (P99 ≤ 配置值)")
t.Log(" - Recovery: 5ms")
t.Log(" - Logging: 10ms")
t.Log(" - RequestID: 5ms")
t.Log(" - Authn: 50ms")
t.Log(" - Authz: 30ms")
t.Log(" - RateLimit: 20ms")
t.Log(" - Idempotency: 30ms")
t.Log(" - Business: 100ms")
t.Log(" - 总超时建议 ≤ 200ms")
}
// ==================== MiddlewareTimeoutContext Tests ====================
func TestNewMiddlewareTimeoutContext_WithNilConfig(t *testing.T) {
ctx := NewMiddlewareTimeoutContext(nil)
if ctx == nil {
t.Fatal("expected non-nil context")
}
if ctx.config == nil {
t.Error("expected config to be set to default")
}
}
func TestNewMiddlewareTimeoutContext_WithCustomConfig(t *testing.T) {
config := &MiddlewareTimeoutConfig{
RecoveryTimeout: 10 * time.Millisecond,
LoggingTimeout: 20 * time.Millisecond,
RequestIDTimeout: 5 * time.Millisecond,
AuthnTimeout: 50 * time.Millisecond,
AuthzTimeout: 30 * time.Millisecond,
RateLimitTimeout: 20 * time.Millisecond,
IdempotencyTimeout: 30 * time.Millisecond,
BusinessTimeout: 100 * time.Millisecond,
DefaultTimeout: 200 * time.Millisecond,
}
ctx := NewMiddlewareTimeoutContext(config)
if ctx == nil {
t.Fatal("expected non-nil context")
}
if ctx.config != config {
t.Error("expected config to be set")
}
if ctx.deadline.IsZero() {
t.Error("expected deadline to be set")
}
}
func TestWithBusinessTimeout(t *testing.T) {
config := &MiddlewareTimeoutConfig{
RecoveryTimeout: 5 * time.Millisecond,
LoggingTimeout: 10 * time.Millisecond,
RequestIDTimeout: 5 * time.Millisecond,
AuthnTimeout: 50 * time.Millisecond,
AuthzTimeout: 30 * time.Millisecond,
RateLimitTimeout: 20 * time.Millisecond,
IdempotencyTimeout: 30 * time.Millisecond,
BusinessTimeout: 100 * time.Millisecond,
DefaultTimeout: 200 * time.Millisecond,
}
ctx := NewMiddlewareTimeoutContext(config)
derivedCtx, cancel := ctx.WithBusinessTimeout()
if derivedCtx == nil {
t.Fatal("expected non-nil derived context")
}
if cancel == nil {
t.Error("expected non-nil cancel function")
}
defer cancel()
// Verify deadline is set
deadline, ok := derivedCtx.Deadline()
if !ok {
t.Error("expected deadline to be set on derived context")
}
if deadline.IsZero() {
t.Error("expected deadline to be non-zero")
}
}
// ==================== WithTimeoutMiddleware Tests ====================
func TestWithTimeoutMiddleware_NormalCompletion(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
w.WriteHeader(http.StatusOK)
})
handler := WithTimeoutMiddleware(nextHandler, 100*time.Millisecond)
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if !nextCalled {
t.Error("next handler should be called")
}
if w.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", w.Code)
}
}
func TestWithTimeoutMiddleware_SetsTraceContext(t *testing.T) {
nextCalled := false
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
})
handler := TracingMiddleware(nextHandler)
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if !nextCalled {
t.Error("next handler should be called")
}
}
func TestWithTimeoutMiddleware_Timeout(t *testing.T) {
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simulate slow handler - 使用足够长的 sleep 确保超时触发
time.Sleep(200 * time.Millisecond)
})
// 超时设置足够短,确保触发超时
handler := WithTimeoutMiddleware(nextHandler, 50*time.Millisecond)
req := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
// The timeout returns 504 Gateway Timeout
if w.Code != http.StatusGatewayTimeout {
t.Errorf("expected status 504, got %d", w.Code)
}
if w.Header().Get("X-Timeout") != "true" {
t.Error("expected X-Timeout header to be set")
}
}
// TestTimeoutResponseWriter_EnsureStarted tests the ensureStarted method
func TestTimeoutResponseWriter_EnsureStarted(t *testing.T) {
w := &TimeoutResponseWriter{
ResponseWriter: httptest.NewRecorder(),
timeout: 100 * time.Millisecond,
}
// Initially not started
if !w.started.IsZero() {
t.Error("started should be zero initially")
}
// Call ensureStarted
w.ensureStarted()
// Should be set now
if w.started.IsZero() {
t.Error("started should be set after ensureStarted")
}
// Calling again should not change it
startedBefore := w.started
w.ensureStarted()
if !w.started.Equal(startedBefore) {
t.Error("started should not change on second call")
}
}
// TestTimeoutResponseWriter_CheckTimeout tests the checkTimeout method
func TestTimeoutResponseWriter_CheckTimeout(t *testing.T) {
w := &TimeoutResponseWriter{
ResponseWriter: httptest.NewRecorder(),
timeout: 50 * time.Millisecond,
}
// Not started - should return false
if w.checkTimeout() {
t.Error("checkTimeout should return false when not started")
}
// Start and check immediately - should return false
w.ensureStarted()
if w.checkTimeout() {
t.Error("checkTimeout should return false immediately after start")
}
// Wait and check - should return true
time.Sleep(60 * time.Millisecond)
if !w.checkTimeout() {
t.Error("checkTimeout should return true after timeout")
}
}
// TestTimeoutResponseWriter_SetTimeoutHeader tests the setTimeoutHeader method
func TestTimeoutResponseWriter_SetTimeoutHeader(t *testing.T) {
recorder := httptest.NewRecorder()
w := &TimeoutResponseWriter{
ResponseWriter: recorder,
timeout: 100 * time.Millisecond,
}
// Initially no timeout header
if recorder.Header().Get("X-Timeout") == "true" {
t.Error("X-Timeout should not be set initially")
}
// Set timeout header
w.setTimeoutHeader()
// Should be set now
if recorder.Header().Get("X-Timeout") != "true" {
t.Error("X-Timeout should be set after setTimeoutHeader")
}
}