提供日志输出、响应输出、HTTP 状态码常量 API: ngx.log, ngx.say, ngx.print, ngx.flush, ngx.exit, ngx.redirect Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
396 lines
10 KiB
Go
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"
|
|
)
|
|
|
|
// mockRequestCtxForLog 创建模拟的 RequestCtx
|
|
func mockRequestCtxForLog() *fasthttp.RequestCtx {
|
|
ctx := &fasthttp.RequestCtx{}
|
|
return ctx
|
|
}
|
|
|
|
// 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")
|
|
}
|