lolly/internal/lua/api_ctx_test.go
xfy d021b0e9fd feat(lua): 实现 ngx.ctx API
提供请求级上下文存储,每请求独立的 Lua table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 12:17:34 +08:00

368 lines
7.8 KiB
Go

// Package lua 提供 ngx.ctx API 测试
package lua
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
)
// TestNgxCtxAPI 测试 ngx.ctx API 基础功能
func TestNgxCtxAPI(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试 ngx.ctx 存在且是一个 table
err = coro.Execute(`
if type(ngx) ~= "table" then
error("ngx is not a table")
end
if type(ngx.ctx) ~= "table" then
error("ngx.ctx is not a table")
end
`)
assert.NoError(t, err)
}
// TestNgxCtxStringValue 测试存储字符串值
func TestNgxCtxStringValue(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试字符串存储
err = coro.Execute(`
ngx.ctx.message = "hello world"
local msg = ngx.ctx.message
if msg ~= "hello world" then
error("string value mismatch: " .. tostring(msg))
end
`)
assert.NoError(t, err)
}
// TestNgxCtxNumberValue 测试存储数字值
func TestNgxCtxNumberValue(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试数字存储
err = coro.Execute(`
ngx.ctx.count = 42
ngx.ctx.pi = 3.14159
local count = ngx.ctx.count
local pi = ngx.ctx.pi
if count ~= 42 then
error("count should be 42, got: " .. tostring(count))
end
if math.abs(pi - 3.14159) > 0.00001 then
error("pi should be 3.14159, got: " .. tostring(pi))
end
`)
assert.NoError(t, err)
}
// TestNgxCtxTableValue 测试存储 table 值
func TestNgxCtxTableValue(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试 table 存储
err = coro.Execute(`
ngx.ctx.data = {
name = "test",
items = {1, 2, 3, 4, 5}
}
local data = ngx.ctx.data
if type(data) ~= "table" then
error("data should be a table")
end
if data.name ~= "test" then
error("data.name should be 'test'")
end
if type(data.items) ~= "table" then
error("data.items should be a table")
end
if data.items[1] ~= 1 then
error("data.items[1] should be 1")
end
if data.items[5] ~= 5 then
error("data.items[5] should be 5")
end
`)
assert.NoError(t, err)
}
// TestNgxCtxFunctionValue 测试存储函数值
func TestNgxCtxFunctionValue(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试函数存储
err = coro.Execute(`
ngx.ctx.handler = function(x)
return x * 2
end
local handler = ngx.ctx.handler
if type(handler) ~= "function" then
error("handler should be a function")
end
local result = handler(21)
if result ~= 42 then
error("handler(21) should return 42, got: " .. tostring(result))
end
`)
assert.NoError(t, err)
}
// TestNgxCtxBooleanValue 测试存储布尔值
func TestNgxCtxBooleanValue(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试布尔值存储
err = coro.Execute(`
ngx.ctx.enabled = true
ngx.ctx.disabled = false
if ngx.ctx.enabled ~= true then
error("enabled should be true")
end
if ngx.ctx.disabled ~= false then
error("disabled should be false")
end
`)
assert.NoError(t, err)
}
// TestNgxCtxNilValue 测试存储和读取 nil 值
func TestNgxCtxNilValue(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试 nil 值
err = coro.Execute(`
ngx.ctx.nothing = nil
local val = ngx.ctx.nothing
if val ~= nil then
error("nothing should be nil")
end
-- 读取不存在的键
local missing = ngx.ctx.missing_key
if missing ~= nil then
error("missing_key should be nil")
end
`)
assert.NoError(t, err)
}
// TestNgxCtxMultipleScripts 测试在同一个脚本中读写 ngx.ctx
// 注意:协程在执行后变成 dead 状态,不能多次执行
func TestNgxCtxMultipleScripts(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 在同一个脚本中设置和读取值(协程只能执行一次)
err = coro.Execute(`
-- 设置值
ngx.ctx.shared_value = "shared"
ngx.ctx.counter = 1
-- 读取并验证值
local val = ngx.ctx.shared_value
if val ~= "shared" then
error("shared_value should be 'shared'")
end
-- 修改值
ngx.ctx.counter = ngx.ctx.counter + 1
if ngx.ctx.counter ~= 2 then
error("counter should be 2")
end
`)
assert.NoError(t, err)
}
// TestNgxCtxNestedTable 测试嵌套 table
func TestNgxCtxNestedTable(t *testing.T) {
var req fasthttp.Request
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&req, nil, nil)
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(ctx)
require.NoError(t, err)
defer coro.Close()
err = coro.SetupSandbox()
require.NoError(t, err)
// 测试嵌套 table
err = coro.Execute(`
ngx.ctx.config = {
database = {
host = "localhost",
port = 5432
},
cache = {
ttl = 3600
}
}
local host = ngx.ctx.config.database.host
if host ~= "localhost" then
error("config.database.host should be 'localhost'")
end
local port = ngx.ctx.config.database.port
if port ~= 5432 then
error("config.database.port should be 5432")
end
ngx.ctx.config.database.port = 3306
if ngx.ctx.config.database.port ~= 3306 then
error("config.database.port should be updated to 3306")
end
`)
assert.NoError(t, err)
}