Apply modern Go patterns across the codebase:
- Replace `interface{}` with `any` (Go 1.18+)
- Use `for range n` instead of `for i := 0; i < n; i++` (Go 1.22+)
- Replace `sort.Slice` with `slices.Sort` from slices package
- Simplify sync.WaitGroup patterns with errgroup where appropriate
- Add Makefile targets for modernize analyzer
Total: 84 files updated, net reduction of 79 lines
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
332 lines
8.7 KiB
Go
332 lines
8.7 KiB
Go
// variable_integration_test.go - 变量系统集成测试
|
||
//
|
||
// 测试变量系统与日志、代理、重写的端到端集成
|
||
//
|
||
// 作者:xfy
|
||
package integration
|
||
|
||
import (
|
||
"bytes"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/config"
|
||
"rua.plus/lolly/internal/logging"
|
||
"rua.plus/lolly/internal/middleware/rewrite"
|
||
"rua.plus/lolly/internal/variable"
|
||
)
|
||
|
||
// TestVariableEndToEndInLogging 测试变量在访问日志中的端到端使用
|
||
func TestVariableEndToEndInLogging(t *testing.T) {
|
||
// 创建内存日志输出
|
||
var buf bytes.Buffer
|
||
|
||
cfg := &config.LoggingConfig{
|
||
Access: config.AccessLogConfig{
|
||
// 使用完整的 nginx 风格格式
|
||
Format: "$remote_addr - $remote_user [$time_local] \"$request_method $request_uri $scheme\" $status $body_bytes_sent \"$http_user_agent\"",
|
||
},
|
||
}
|
||
|
||
logger := logging.New(cfg)
|
||
|
||
// 模拟请求
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/api/users?page=1")
|
||
ctx.Request.Header.SetMethod("GET")
|
||
ctx.Request.Header.SetHost("example.com")
|
||
ctx.Request.Header.Set("User-Agent", "TestAgent/1.0")
|
||
|
||
// 记录访问日志
|
||
logger.LogAccess(ctx, 200, 1024, 50*time.Millisecond)
|
||
|
||
// 验证输出(这里主要验证不 panic)
|
||
_ = buf.String()
|
||
t.Log("Logging with variables completed successfully")
|
||
}
|
||
|
||
// TestVariableInProxyHeaders 测试代理请求头中的变量
|
||
func TestVariableInProxyHeaders(t *testing.T) {
|
||
// 创建请求
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/api/test")
|
||
ctx.Request.Header.SetMethod("POST")
|
||
ctx.Request.Header.SetHost("api.example.com")
|
||
ctx.Request.Header.Set("X-Custom-Header", "original")
|
||
|
||
// 测试变量展开
|
||
vc := variable.NewContext(ctx)
|
||
defer variable.ReleaseContext(vc)
|
||
|
||
// 模拟代理配置中的 header 设置
|
||
tests := []struct {
|
||
template string
|
||
contains []string
|
||
}{
|
||
{"X-Forwarded-Host: $host", []string{"api.example.com"}},
|
||
{"X-Real-IP: $remote_addr", []string{"0.0.0.0"}},
|
||
{"X-Request-ID: $request_id", []string{"-"}}, // 未设置时为 -
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
result := vc.Expand(tt.template)
|
||
for _, expected := range tt.contains {
|
||
if !strings.Contains(result, expected) {
|
||
t.Errorf("Expand(%q) = %q, expected to contain %q", tt.template, result, expected)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestVariableInRewriteRules 测试重写规则中的变量
|
||
func TestVariableInRewriteRules(t *testing.T) {
|
||
// 创建带有变量的重写规则
|
||
rules := []config.RewriteRule{
|
||
{
|
||
Pattern: "^/api/(.*)$",
|
||
Replacement: "/v1/$1",
|
||
Flag: "break",
|
||
},
|
||
{
|
||
Pattern: "^/redirect/(.*)$",
|
||
Replacement: "/new/$1",
|
||
Flag: "redirect",
|
||
},
|
||
}
|
||
|
||
mw, err := rewrite.New(rules)
|
||
if err != nil {
|
||
t.Fatalf("failed to create rewrite middleware: %v", err)
|
||
}
|
||
|
||
// 测试第一个规则
|
||
t.Run("rewrite_with_capture", func(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/api/users")
|
||
ctx.Request.Header.SetMethod("GET")
|
||
|
||
var capturedPath string
|
||
next := func(c *fasthttp.RequestCtx) {
|
||
capturedPath = string(c.Path())
|
||
}
|
||
|
||
handler := mw.Process(next)
|
||
handler(ctx)
|
||
|
||
if capturedPath != "/v1/users" {
|
||
t.Errorf("expected path '/v1/users', got %q", capturedPath)
|
||
}
|
||
})
|
||
|
||
// 测试重定向规则
|
||
t.Run("redirect_with_capture", func(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/redirect/old-page")
|
||
ctx.Request.Header.SetMethod("GET")
|
||
|
||
handler := mw.Process(func(_ *fasthttp.RequestCtx) {
|
||
t.Error("should not reach next handler for redirect")
|
||
})
|
||
handler(ctx)
|
||
|
||
// 验证重定向状态码
|
||
if ctx.Response.StatusCode() != 302 {
|
||
t.Errorf("expected status 302, got %d", ctx.Response.StatusCode())
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestVariableCompatibilityWithNginx 测试与 nginx 变量格式的兼容性
|
||
func TestVariableCompatibilityWithNginx(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/test/path?foo=bar&baz=qux")
|
||
ctx.Request.Header.SetMethod("POST")
|
||
ctx.Request.Header.SetHost("example.com")
|
||
ctx.Request.Header.Set("User-Agent", "TestAgent/1.0")
|
||
ctx.Request.Header.Set("Referer", "http://referrer.com")
|
||
|
||
// 设置响应信息
|
||
variable.SetResponseInfoInContext(ctx, 201, 2048, 100000000) // 100ms
|
||
|
||
vc := variable.NewContext(ctx)
|
||
defer variable.ReleaseContext(vc)
|
||
|
||
// 设置 HTTP 头变量(logging 中会自动设置这些)
|
||
vc.Set("http_referer", "http://referrer.com")
|
||
vc.Set("http_user_agent", "TestAgent/1.0")
|
||
vc.Set("remote_user", "-")
|
||
|
||
// 测试 nginx 风格的组合日志格式
|
||
logFormat := "$remote_addr - $remote_user [$time_local] \"$request_method $request_uri $scheme\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\""
|
||
result := vc.Expand(logFormat)
|
||
|
||
// 验证结果包含期望的部分
|
||
expectedParts := []string{
|
||
"POST", // method
|
||
"/test/path", // path
|
||
"foo=bar", // query string
|
||
"http", // scheme
|
||
"201", // status
|
||
"2048", // body_bytes_sent
|
||
"http://referrer.com", // referer
|
||
"TestAgent/1.0", // user agent
|
||
}
|
||
|
||
for _, part := range expectedParts {
|
||
if !strings.Contains(result, part) {
|
||
t.Errorf("log output missing %q: got %q", part, result)
|
||
}
|
||
}
|
||
|
||
t.Logf("Generated log: %s", result)
|
||
}
|
||
|
||
// TestVariablePerformance 测试变量展开性能
|
||
func TestVariablePerformance(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/api/v1/users/123?active=true")
|
||
ctx.Request.Header.SetMethod("GET")
|
||
ctx.Request.Header.SetHost("api.example.com")
|
||
|
||
vc := variable.NewContext(ctx)
|
||
defer variable.ReleaseContext(vc)
|
||
|
||
// 常见日志格式模板
|
||
template := "$remote_addr - $remote_user [$time_local] \"$request_method $request_uri $scheme\" $status $body_bytes_sent \"$http_user_agent\""
|
||
|
||
// 执行多次展开
|
||
start := time.Now()
|
||
iterations := 10000
|
||
for range iterations {
|
||
_ = vc.Expand(template)
|
||
}
|
||
elapsed := time.Since(start)
|
||
|
||
// 计算平均时间
|
||
avg := elapsed / time.Duration(iterations)
|
||
t.Logf("Average expansion time: %v (iterations: %d)", avg, iterations)
|
||
|
||
// 验证性能在合理范围内(< 1μs 每次)
|
||
if avg > time.Microsecond {
|
||
t.Errorf("average time %v exceeds 1μs", avg)
|
||
}
|
||
}
|
||
|
||
// TestVariableEdgeCases 测试变量的边界情况
|
||
func TestVariableEdgeCases(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
setup func(*fasthttp.RequestCtx)
|
||
template string
|
||
expected string
|
||
}{
|
||
{
|
||
name: "empty_template",
|
||
setup: func(ctx *fasthttp.RequestCtx) {
|
||
ctx.Request.SetRequestURI("/test")
|
||
},
|
||
template: "",
|
||
expected: "",
|
||
},
|
||
{
|
||
name: "no_variables",
|
||
setup: func(ctx *fasthttp.RequestCtx) {
|
||
ctx.Request.SetRequestURI("/test")
|
||
},
|
||
template: "static text without variables",
|
||
expected: "static text without variables",
|
||
},
|
||
{
|
||
name: "undefined_variable",
|
||
setup: func(ctx *fasthttp.RequestCtx) {
|
||
ctx.Request.SetRequestURI("/test")
|
||
},
|
||
template: "$undefined_var",
|
||
expected: "$undefined_var", // 保持原样
|
||
},
|
||
{
|
||
name: "mixed_defined_undefined",
|
||
setup: func(ctx *fasthttp.RequestCtx) {
|
||
ctx.Request.SetRequestURI("/test")
|
||
ctx.Request.Header.SetHost("example.com")
|
||
},
|
||
template: "$host-$undefined",
|
||
expected: "example.com-$undefined",
|
||
},
|
||
{
|
||
name: "special_characters_in_value",
|
||
setup: func(ctx *fasthttp.RequestCtx) {
|
||
ctx.Request.SetRequestURI("/test%20path")
|
||
ctx.Request.Header.SetHost("example.com")
|
||
},
|
||
template: "$uri",
|
||
expected: "/test path", // fasthttp Path() 返回解码后的路径
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
tt.setup(ctx)
|
||
|
||
vc := variable.NewContext(ctx)
|
||
defer variable.ReleaseContext(vc)
|
||
|
||
result := vc.Expand(tt.template)
|
||
if result != tt.expected {
|
||
t.Errorf("Expand(%q) = %q, want %q", tt.template, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestVariableAllBuiltins 测试所有内置变量
|
||
func TestVariableAllBuiltins(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.SetRequestURI("/test/path?foo=bar")
|
||
ctx.Request.Header.SetMethod("GET")
|
||
ctx.Request.Header.SetHost("example.com")
|
||
|
||
// 设置响应信息
|
||
variable.SetResponseInfoInContext(ctx, 200, 1024, 50000000) // 50ms
|
||
|
||
vc := variable.NewContext(ctx)
|
||
defer variable.ReleaseContext(vc)
|
||
|
||
// 测试所有内置变量
|
||
builtinVars := []string{
|
||
"host",
|
||
"remote_addr",
|
||
"remote_port",
|
||
"request_uri",
|
||
"uri",
|
||
"args",
|
||
"request_method",
|
||
"scheme",
|
||
"server_name",
|
||
"server_port",
|
||
"status",
|
||
"body_bytes_sent",
|
||
"request_time",
|
||
"time_local",
|
||
"time_iso8601",
|
||
"request_id",
|
||
}
|
||
|
||
for _, varName := range builtinVars {
|
||
t.Run(varName, func(t *testing.T) {
|
||
value, ok := vc.Get(varName)
|
||
if !ok {
|
||
t.Errorf("builtin variable %q not found", varName)
|
||
return
|
||
}
|
||
if value == "" && varName != "args" {
|
||
t.Logf("Warning: %q is empty", varName)
|
||
}
|
||
t.Logf("%s = %q", varName, value)
|
||
})
|
||
}
|
||
}
|