lolly/internal/lua/api_log_test.go
xfy d21e27fbac fix(lint): 修复 golangci-lint 错误 (119 -> 0 issues)
主要修复:
- errcheck: defer Close 使用 //nolint:errcheck,类型断言改为 ok 检查
- govet fieldalignment: 调整结构体字段顺序优化内存布局
- revive unused-parameter: 将未使用参数改为 _
- exhaustive: 添加缺失的 switch case 或 default
- goconst: 提取重复字符串为常量 (accessAllow, accessDeny 等)
- staticcheck SA9003: 修复空分支逻辑
- gofmt: 运行 gofmt -w 格式化
- nolintlint: 修复 nolint 注释格式

其他改进:
- 更新 .golangci.yml 配置,启用更严格的检查
- 移除未使用的代码和导入
- 简化测试辅助函数调用

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 16:15:31 +08:00

396 lines
10 KiB
Go

// Package lua 提供 ngx.log API 测试
package lua
import (
"bytes"
"testing"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
glua "github.com/yuin/gopher-lua"
"rua.plus/lolly/internal/testutil"
)
// mockRequestCtxForLog 创建模拟的 RequestCtx
func mockRequestCtxForLog() *fasthttp.RequestCtx {
return testutil.NewRequestCtx("GET", "/")
}
// TestNgxLogLevelConstants 测试日志级别常量
func TestNgxLogLevelConstants(t *testing.T) {
// 验证日志级别常量值
assert.Equal(t, 0, LogStderr)
assert.Equal(t, 1, LogEmerg)
assert.Equal(t, 2, LogAlert)
assert.Equal(t, 3, LogCrit)
assert.Equal(t, 4, LogErr)
assert.Equal(t, 5, LogWarn)
assert.Equal(t, 6, LogNotice)
assert.Equal(t, 7, LogInfo)
assert.Equal(t, 8, LogDebug)
}
// TestNgxHTTPStatusConstants 测试 HTTP 状态码常量
func TestNgxHTTPStatusConstants(t *testing.T) {
// 验证常用 HTTP 状态码
assert.Equal(t, 200, HTTPOK)
assert.Equal(t, 201, HTTPCreated)
assert.Equal(t, 301, HTTPMovedPermanently)
assert.Equal(t, 302, HTTPFound)
assert.Equal(t, 303, HTTPSeeOther)
assert.Equal(t, 307, HTTPTemporaryRedirect)
assert.Equal(t, 308, HTTPPermanentRedirect)
assert.Equal(t, 400, HTTPBadRequest)
assert.Equal(t, 404, HTTPNotFound)
assert.Equal(t, 500, HTTPInternalServerError)
}
// TestNgxLogAPIRegistration 测试 API 注册
func TestNgxLogAPIRegistration(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
// 创建测试请求上下文
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
// 创建 zerolog logger
var buf bytes.Buffer
logger := zerolog.New(&buf)
// 创建 API 实例
api := newNgxLogAPI(ctx, luaCtx, &logger)
// 在 Lua 状态机中注册 API
L := engine.L
RegisterNgxLogAPI(L, api)
// 验证 ngx 表已创建
ngx := L.GetGlobal("ngx")
require.NotEqual(t, nil, ngx)
// 验证日志级别常量已注册
ngxTable := ngx.(*glua.LTable)
assert.Equal(t, glua.LNumber(LogStderr), ngxTable.RawGetString("STDERR"))
assert.Equal(t, glua.LNumber(LogEmerg), ngxTable.RawGetString("EMERG"))
assert.Equal(t, glua.LNumber(LogErr), ngxTable.RawGetString("ERR"))
assert.Equal(t, glua.LNumber(LogWarn), ngxTable.RawGetString("WARN"))
assert.Equal(t, glua.LNumber(LogInfo), ngxTable.RawGetString("INFO"))
assert.Equal(t, glua.LNumber(LogDebug), ngxTable.RawGetString("DEBUG"))
// 验证 HTTP 状态码常量已注册
assert.Equal(t, glua.LNumber(HTTPOK), ngxTable.RawGetString("HTTP_OK"))
assert.Equal(t, glua.LNumber(HTTPNotFound), ngxTable.RawGetString("HTTP_NOT_FOUND"))
assert.Equal(t, glua.LNumber(HTTPInternalServerError), ngxTable.RawGetString("HTTP_INTERNAL_SERVER_ERROR"))
// 验证函数已注册
assert.NotEqual(t, glua.LNil, ngxTable.RawGetString("log"))
assert.NotEqual(t, glua.LNil, ngxTable.RawGetString("say"))
assert.NotEqual(t, glua.LNil, ngxTable.RawGetString("print"))
assert.NotEqual(t, glua.LNil, ngxTable.RawGetString("flush"))
assert.NotEqual(t, glua.LNil, ngxTable.RawGetString("exit"))
assert.NotEqual(t, glua.LNil, ngxTable.RawGetString("redirect"))
}
// TestNgxLogAPILog 测试 ngx.log API
func TestNgxLogAPILog(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试日志输出
err = L.DoString(`
ngx.log(ngx.INFO, "test message")
`)
// 日志调用不应返回错误
assert.NoError(t, err)
// 验证日志内容
logOutput := buf.String()
assert.Contains(t, logOutput, "test message")
}
// TestNgxLogAPISay 测试 ngx.say API
func TestNgxLogAPISay(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试 say 输出
err = L.DoString(`
ngx.say("Hello, ")
ngx.say("World!")
`)
require.NoError(t, err)
// 验证输出缓冲包含换行符
assert.Equal(t, "Hello, \nWorld!\n", string(luaCtx.OutputBuffer))
}
// TestNgxLogAPIPrint 测试 ngx.print API
func TestNgxLogAPIPrint(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试 print 输出
err = L.DoString(`
ngx.print("Hello")
ngx.print(", ")
ngx.print("World")
`)
require.NoError(t, err)
// 验证输出缓冲不包含换行符
assert.Equal(t, "Hello, World", string(luaCtx.OutputBuffer))
}
// TestNgxLogAPIFlush 测试 ngx.flush API
func TestNgxLogAPIFlush(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试 flush
err = L.DoString(`
ngx.print("before flush")
local ok = ngx.flush()
assert(ok == true, "flush should return true")
`)
require.NoError(t, err)
}
// TestNgxLogAPIExit 测试 ngx.exit API
func TestNgxLogAPIExit(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试 exit - 应该抛出错误
err = L.DoString(`
ngx.exit(ngx.HTTP_OK)
`)
// exit 应该返回错误
assert.Error(t, err)
assert.Contains(t, err.Error(), "ngx.exit")
// 验证状态码已设置
assert.Equal(t, 200, ctx.Response.StatusCode())
}
// TestNgxLogAPIRedirect 测试 ngx.redirect API
func TestNgxLogAPIRedirect(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试 redirect - 默认状态码 302
err = L.DoString(`
ngx.redirect("/new/path")
`)
// redirect 应该返回错误以终止执行
assert.Error(t, err)
assert.Contains(t, err.Error(), "ngx.redirect")
// 验证重定向头和状态码
assert.Equal(t, 302, ctx.Response.StatusCode())
assert.Equal(t, "/new/path", string(ctx.Response.Header.Peek("Location")))
}
// TestNgxLogAPIRedirectWithStatus 测试带状态码的 redirect
func TestNgxLogAPIRedirectWithStatus(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试 redirect - 301
err = L.DoString(`
ngx.redirect("/permanent/path", ngx.HTTP_MOVED_PERMANENTLY)
`)
assert.Error(t, err)
// 验证状态码
assert.Equal(t, 301, ctx.Response.StatusCode())
}
// TestNgxLogAPIRedirectInvalidStatus 测试无效状态码的 redirect
func TestNgxLogAPIRedirectInvalidStatus(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试 redirect - 无效状态码 (400)
err = L.DoString(`
ngx.redirect("/path", 400)
`)
// 应该返回参数错误
assert.Error(t, err)
}
// TestNgxLogAPILogLevels 测试不同日志级别
func TestNgxLogAPILogLevels(t *testing.T) {
testCases := []struct {
level int
expected zerolog.Level
}{
{LogEmerg, zerolog.FatalLevel},
{LogAlert, zerolog.FatalLevel},
{LogCrit, zerolog.FatalLevel},
{LogErr, zerolog.ErrorLevel},
{LogWarn, zerolog.WarnLevel},
{LogNotice, zerolog.InfoLevel},
{LogInfo, zerolog.InfoLevel},
{LogDebug, zerolog.DebugLevel},
{999, zerolog.InfoLevel}, // 未知级别默认为 Info
}
for _, tc := range testCases {
result := LogLevelToZerolog(tc.level)
assert.Equal(t, tc.expected, result, "level %d should map to %v", tc.level, tc.expected)
}
}
// TestNgxLogAPIWithoutLogger 测试无 logger 的情况
func TestNgxLogAPIWithoutLogger(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
// 创建无 logger 的 API
api := newNgxLogAPI(ctx, luaCtx, nil)
L := engine.L
RegisterNgxLogAPI(L, api)
// 测试日志 - 不应 panic
err = L.DoString(`
ngx.log(ngx.INFO, "message without logger")
`)
assert.NoError(t, err)
}
// TestNgxLogAPIIntegration 集成测试
func TestNgxLogAPIIntegration(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
ctx := mockRequestCtxForLog()
luaCtx := NewContext(engine, ctx)
var buf bytes.Buffer
logger := zerolog.New(&buf)
api := newNgxLogAPI(ctx, luaCtx, &logger)
L := engine.L
RegisterNgxLogAPI(L, api)
// 综合测试
err = L.DoString(`
-- 记录日志
ngx.log(ngx.INFO, "Starting request")
-- 输出内容
ngx.say("Line 1")
ngx.print("Line 2")
ngx.say("")
-- 使用常量
ngx.say("HTTP OK: " .. ngx.HTTP_OK)
ngx.say("HTTP NOT FOUND: " .. ngx.HTTP_NOT_FOUND)
`)
require.NoError(t, err)
// 验证输出
output := string(luaCtx.OutputBuffer)
assert.Contains(t, output, "Line 1")
assert.Contains(t, output, "Line 2")
assert.Contains(t, output, "HTTP OK: 200")
assert.Contains(t, output, "HTTP NOT FOUND: 404")
}