Files
tyc-server/app/main/api/internal/middleware/logging/userOperationMiddleware_test.go
2025-08-31 14:18:31 +08:00

417 lines
12 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 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&param2=value2&param3=", 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)
}
}