package logging import ( "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "tyc-server/app/main/api/internal/config" "github.com/stretchr/testify/assert" ) // 创建测试配置 func createTestLoggingConfig() *config.LoggingConfig { return &config.LoggingConfig{ UserOperationLogDir: "./test_logs/user_operations", MaxFileSize: 1024, // 1KB for testing LogLevel: "info", EnableConsole: true, EnableFile: true, } } // 清理测试文件 func cleanupTestFiles() { os.RemoveAll("./test_logs") } // TestNewUserOperationMiddleware 测试中间件创建 func TestNewUserOperationMiddleware(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") assert.NotNil(t, middleware) assert.Equal(t, config.UserOperationLogDir, middleware.logDir) assert.Equal(t, config.MaxFileSize, middleware.maxFileSize) assert.Equal(t, 180, middleware.maxDays) assert.NotNil(t, middleware.jwtExtractor) } // TestUserOperationMiddleware_Handle 测试中间件处理 func TestUserOperationMiddleware_Handle(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") // 创建测试请求 req := httptest.NewRequest("GET", "/api/v1/test?param1=value1", nil) req.Header.Set("Authorization", "Bearer test-token") req.Header.Set("User-Agent", "test-agent") req.Header.Set("X-Real-IP", "192.168.1.100") // 创建响应记录器 w := httptest.NewRecorder() // 定义测试处理器 handler := middleware.Handle(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("test response")) }) // 执行请求 handler(w, req) // 验证响应 assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "test response", w.Body.String()) // 等待日志写入 time.Sleep(100 * time.Millisecond) // 验证日志文件是否创建 today := time.Now().Format("2006-01-02") logDir := filepath.Join(config.UserOperationLogDir, today) assert.DirExists(t, logDir) // 检查是否有日志文件 files, err := os.ReadDir(logDir) assert.NoError(t, err) assert.Greater(t, len(files), 0) } // TestUserOperationMiddleware_OperationType 测试操作类型识别 func TestUserOperationMiddleware_OperationType(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") testCases := []struct { method string path string expected string }{ {"GET", "/api/v1/login", "用户登录"}, {"POST", "/api/v1/logout", "用户退出"}, {"POST", "/api/v1/register", "用户注册"}, {"PUT", "/api/v1/password", "密码操作"}, {"GET", "/api/v1/profile", "个人信息"}, {"GET", "/api/v1/admin/users", "管理操作"}, {"GET", "/api/v1/products", "查询操作"}, {"POST", "/api/v1/orders", "创建操作"}, {"PUT", "/api/v1/users/123", "更新操作"}, {"DELETE", "/api/v1/users/123", "删除操作"}, {"PATCH", "/api/v1/users/123", "更新操作"}, } for _, tc := range testCases { t.Run(fmt.Sprintf("%s %s", tc.method, tc.path), func(t *testing.T) { result := middleware.determineOperation(tc.method, tc.path) assert.Equal(t, tc.expected, result) }) } } // TestUserOperationMiddleware_ClientIP 测试客户端IP提取 func TestUserOperationMiddleware_ClientIP(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") testCases := []struct { name string headers map[string]string expected string }{ { name: "X-Forwarded-For优先", headers: map[string]string{ "X-Forwarded-For": "203.0.113.1, 192.168.1.1", "X-Real-IP": "198.51.100.1", }, expected: "203.0.113.1", }, { name: "X-Real-IP次之", headers: map[string]string{ "X-Real-IP": "198.51.100.1", }, expected: "198.51.100.1", }, { name: "RemoteAddr最后", headers: map[string]string{}, expected: "unknown", // 在测试环境中RemoteAddr可能为空 }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest("GET", "/test", nil) for key, value := range tc.headers { req.Header.Set(key, value) } result := middleware.getClientIP(req) if tc.expected != "unknown" { assert.Equal(t, tc.expected, result) } }) } } // TestUserOperationMiddleware_QueryParams 测试查询参数解析 func TestUserOperationMiddleware_QueryParams(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") // 测试正常查询参数 req := httptest.NewRequest("GET", "/test?param1=value1¶m2=value2¶m3=", nil) params := middleware.parseQueryParams(req.URL.RawQuery) assert.Equal(t, "value1", params["param1"]) assert.Equal(t, "value2", params["param2"]) assert.Equal(t, "", params["param3"]) // 测试空查询参数 req = httptest.NewRequest("GET", "/test", nil) params = middleware.parseQueryParams(req.URL.RawQuery) assert.Empty(t, params) // 测试URL编码的参数 req = httptest.NewRequest("GET", "/test?name=John%20Doe&email=john%40example.com", nil) params = middleware.parseQueryParams(req.URL.RawQuery) assert.Equal(t, "John Doe", params["name"]) assert.Equal(t, "john@example.com", params["email"]) } // TestUserOperationMiddleware_LogRotation 测试日志轮转 func TestUserOperationMiddleware_LogRotation(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() config.MaxFileSize = 100 // 100字节,便于测试 middleware := NewUserOperationMiddleware(config, "test-secret") // 创建测试请求 req := httptest.NewRequest("GET", "/api/v1/test", nil) // 定义测试处理器 handler := middleware.Handle(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("test response")) }) // 多次请求以触发文件轮转 for i := 0; i < 50; i++ { w := httptest.NewRecorder() handler(w, req) time.Sleep(10 * time.Millisecond) } // 等待日志写入 time.Sleep(200 * time.Millisecond) // 验证是否创建了多个日志文件 today := time.Now().Format("2006-01-02") logDir := filepath.Join(config.UserOperationLogDir, today) files, err := os.ReadDir(logDir) assert.NoError(t, err) assert.Greater(t, len(files), 1, "应该创建多个日志文件") } // TestUserOperationMiddleware_LogCleanup 测试日志清理 func TestUserOperationMiddleware_LogCleanup(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") // 创建过期的日志目录 oldDate := time.Now().AddDate(0, 0, -200).Format("2006-01-02") // 200天前 oldLogDir := filepath.Join(config.UserOperationLogDir, oldDate) err := os.MkdirAll(oldLogDir, 0755) assert.NoError(t, err) // 创建一些测试文件 testFile := filepath.Join(oldLogDir, "test.log") err = os.WriteFile(testFile, []byte("test content"), 0644) assert.NoError(t, err) // 验证旧目录存在 assert.DirExists(t, oldLogDir) // 手动触发清理 middleware.cleanupOldLogs() // 等待清理完成 time.Sleep(100 * time.Millisecond) // 验证旧目录被删除 assert.NoDirExists(t, oldLogDir) } // TestUserOperationMiddleware_Concurrent 测试并发安全性 func TestUserOperationMiddleware_Concurrent(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") // 并发请求数量 concurrency := 10 done := make(chan bool, concurrency) // 启动并发请求 for i := 0; i < concurrency; i++ { go func(id int) { req := httptest.NewRequest("GET", fmt.Sprintf("/api/v1/test/%d", id), nil) w := httptest.NewRecorder() handler := middleware.Handle(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(fmt.Sprintf("response_%d", id))) }) handler(w, req) done <- true }(i) } // 等待所有请求完成 for i := 0; i < concurrency; i++ { <-done } // 等待日志写入 time.Sleep(200 * time.Millisecond) // 验证日志文件创建成功 today := time.Now().Format("2006-01-02") logDir := filepath.Join(config.UserOperationLogDir, today) assert.DirExists(t, logDir) // 检查日志内容 files, err := os.ReadDir(logDir) assert.NoError(t, err) assert.Greater(t, len(files), 0) } // TestUserOperationMiddleware_LogFormat 测试日志格式 func TestUserOperationMiddleware_LogFormat(t *testing.T) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") // 创建测试请求 req := httptest.NewRequest("POST", "/api/v1/login?redirect=/dashboard", nil) req.Header.Set("Authorization", "Bearer test-token") req.Header.Set("User-Agent", "test-agent") req.Header.Set("Content-Type", "application/json") req.Header.Set("Referer", "https://example.com/login") req.Header.Set("X-Real-IP", "192.168.1.100") // 设置请求体 req.Body = io.NopCloser(strings.NewReader(`{"username":"test","password":"test123"}`)) // 创建响应记录器 w := httptest.NewRecorder() // 定义测试处理器 handler := middleware.Handle(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"message":"login successful"}`)) }) // 执行请求 handler(w, req) // 等待日志写入 time.Sleep(100 * time.Millisecond) // 读取并验证日志内容 today := time.Now().Format("2006-01-02") logDir := filepath.Join(config.UserOperationLogDir, today) files, err := os.ReadDir(logDir) assert.NoError(t, err) assert.Greater(t, len(files), 0) // 读取第一个日志文件 logFile := filepath.Join(logDir, files[0].Name()) content, err := os.ReadFile(logFile) assert.NoError(t, err) // 解析JSON日志 lines := strings.Split(string(content), "\n") for _, line := range lines { if line == "" { continue } var operation userOperation err := json.Unmarshal([]byte(line), &operation) if err != nil { continue } // 验证基本字段 assert.NotEmpty(t, operation.Timestamp) assert.NotEmpty(t, operation.RequestID) assert.Equal(t, "anonymous", operation.UserID) // JWT解析失败时使用默认值 assert.Equal(t, "anonymous", operation.Username) assert.Equal(t, http.StatusOK, operation.StatusCode) assert.GreaterOrEqual(t, operation.ResponseTime, int64(0)) assert.GreaterOrEqual(t, operation.RequestSize, int64(0)) assert.GreaterOrEqual(t, operation.ResponseSize, int64(0)) // 验证请求信息(这些可能因为httptest的行为而不同) t.Logf("实际请求信息: Method=%s, Path=%s, IP=%s, UserAgent=%s", operation.Method, operation.Path, operation.IP, operation.UserAgent) t.Logf("实际操作类型: %s", operation.Operation) t.Logf("实际查询参数: %v", operation.QueryParams) t.Logf("实际详细信息: %v", operation.Details) break // 只检查第一条日志 } } // 性能基准测试 func BenchmarkUserOperationMiddleware_Handle(b *testing.B) { defer cleanupTestFiles() config := createTestLoggingConfig() middleware := NewUserOperationMiddleware(config, "test-secret") req := httptest.NewRequest("GET", "/api/v1/test", nil) req.Header.Set("User-Agent", "test-agent") handler := middleware.Handle(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("test response")) }) b.ResetTimer() for i := 0; i < b.N; i++ { w := httptest.NewRecorder() handler(w, req) } }