417 lines
12 KiB
Go
417 lines
12 KiB
Go
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)
|
||
}
|
||
}
|