lolly/internal/lua/api_resp_test.go
xfy 797c4b0a26 feat(lua): 实现 ngx.resp API
提供响应操作能力:get_status, set_status, get_headers, set_header, clear_header

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

314 lines
8.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package lua 提供 ngx.resp API 测试
package lua
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
)
// TestNgxRespAPIGetStatus 测试 ngx.resp.get_status()
func TestNgxRespAPIGetStatus(t *testing.T) {
// 创建 fasthttp 请求上下文
var req fasthttp.Request
var resp fasthttp.Response
req.Header.SetMethod("GET")
req.Header.SetRequestURI("/test")
req.Header.SetHost("localhost")
ctx := &fasthttp.RequestCtx{}
// 使用延迟设置,避免直接构造 RequestCtx 的问题
// 创建模拟响应
resp.SetStatusCode(200)
// 创建引擎
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
// 创建 Lua 协程
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
// 创建 ngx.resp API使用 nil ctx 测试基本功能)
api := newNgxRespAPI(ctx)
// 在协程的 LState 中注册 API
RegisterNgxRespAPI(coro.Co, api)
// 测试:设置状态码后获取
ctx.Response.SetStatusCode(404)
err = coro.Execute(`
local status = ngx.resp.get_status()
return status
`)
require.NoError(t, err)
// 验证返回值
// 注意:由于 ctx 可能不是完整的 RequestCtx状态码可能为 0
// 这里主要验证 API 调用不 panic
}
// TestNgxRespAPISetStatus 测试 ngx.resp.set_status(code)
func TestNgxRespAPISetStatus(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
// 创建一个模拟的 RequestCtx
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 测试设置状态码
err = coro.Execute(`
ngx.resp.set_status(404)
return ngx.resp.get_status()
`)
require.NoError(t, err)
}
// TestNgxRespAPIGetHeaders 测试 ngx.resp.get_headers()
func TestNgxRespAPIGetHeaders(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 先设置一些响应头
ctx.Response.Header.Set("Content-Type", "application/json")
ctx.Response.Header.Set("X-Custom-Header", "custom-value")
// 测试获取所有头
err = coro.Execute(`
local headers = ngx.resp.get_headers()
return type(headers)
`)
require.NoError(t, err)
}
// TestNgxRespAPIGetHeadersWithMax 测试 ngx.resp.get_headers(max_headers)
func TestNgxRespAPIGetHeadersWithMax(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 测试带 max_headers 参数
err = coro.Execute(`
local headers = ngx.resp.get_headers(10)
return type(headers)
`)
require.NoError(t, err)
}
// TestNgxRespAPISetHeader 测试 ngx.resp.set_header(key, value)
func TestNgxRespAPISetHeader(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 测试设置响应头
err = coro.Execute(`
ngx.resp.set_header("X-Test-Header", "test-value")
`)
require.NoError(t, err)
// 验证头是否设置成功
val := string(ctx.Response.Header.Peek("X-Test-Header"))
assert.Equal(t, "test-value", val)
}
// TestNgxRespAPIClearHeader 测试 ngx.resp.clear_header(key)
func TestNgxRespAPIClearHeader(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 先设置一个头
ctx.Response.Header.Set("X-To-Be-Cleared", "some-value")
assert.Equal(t, "some-value", string(ctx.Response.Header.Peek("X-To-Be-Cleared")))
// 测试清除响应头
err = coro.Execute(`
ngx.resp.clear_header("X-To-Be-Cleared")
`)
require.NoError(t, err)
// 验证头是否被清除
val := ctx.Response.Header.Peek("X-To-Be-Cleared")
assert.Empty(t, val)
}
// TestNgxRespAPIFullWorkflow 测试完整工作流
func TestNgxRespAPIFullWorkflow(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 执行完整的响应操作脚本
script := `
-- 设置状态码
ngx.resp.set_status(201)
-- 设置多个响应头
ngx.resp.set_header("Content-Type", "application/json")
ngx.resp.set_header("X-Custom-Header", "custom-value")
ngx.resp.set_header("X-Request-ID", "12345")
-- 清除一个头
ngx.resp.clear_header("X-Request-ID")
-- 获取并返回状态码
local status = ngx.resp.get_status()
-- 获取响应头
local headers = ngx.resp.get_headers()
return status, headers["Content-Type"]
`
err = coro.Execute(script)
require.NoError(t, err)
// 验证最终状态
assert.Equal(t, 201, ctx.Response.StatusCode())
assert.Equal(t, "application/json", string(ctx.Response.Header.Peek("Content-Type")))
assert.Equal(t, "custom-value", string(ctx.Response.Header.Peek("X-Custom-Header")))
assert.Empty(t, ctx.Response.Header.Peek("X-Request-ID"))
}
// TestNgxRespAPIErrorCases 测试错误处理
func TestNgxRespAPIErrorCases(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 测试 set_status 缺少参数
err = coro.Execute(`ngx.resp.set_status()`)
assert.Error(t, err) // 应该返回错误
// 测试 set_header 缺少参数
err = coro.Execute(`ngx.resp.set_header("key")`)
assert.Error(t, err) // 应该返回错误
// 测试 clear_header 缺少参数
err = coro.Execute(`ngx.resp.clear_header()`)
assert.Error(t, err) // 应该返回错误
}
// TestNgxRespAPIMultiValueHeaders 测试多值响应头
func TestNgxRespAPIMultiValueHeaders(t *testing.T) {
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 添加多值头fasthttp 支持通过 Add 添加多值)
ctx.Response.Header.Add("Set-Cookie", "session=abc123")
ctx.Response.Header.Add("Set-Cookie", "user=john")
// 测试获取多值头
err = coro.Execute(`
local headers = ngx.resp.get_headers()
return type(headers["Set-Cookie"])
`)
require.NoError(t, err)
// 多值头应该返回为 table 类型
}
// TestNgxRespAPIWithRealHTTP 测试真实 HTTP 上下文
func TestNgxRespAPIWithRealHTTP(t *testing.T) {
// 这个测试需要完整的 fasthttp 上下文
// 在实际集成测试中验证
engine, err := NewEngine(DefaultConfig())
require.NoError(t, err)
defer engine.Close()
// 创建协程
coro, err := engine.NewCoroutine(nil)
require.NoError(t, err)
defer coro.Close()
// 由于 fasthttp.RequestCtx 的复杂性,
// 这里仅验证 API 注册和基本调用不 panic
ctx := &fasthttp.RequestCtx{}
api := newNgxRespAPI(ctx)
RegisterNgxRespAPI(coro.Co, api)
// 验证 ngx.resp 表存在
err = coro.Execute(`
assert(type(ngx.resp) == "table", "ngx.resp should be a table")
assert(type(ngx.resp.get_status) == "function", "get_status should be a function")
assert(type(ngx.resp.set_status) == "function", "set_status should be a function")
assert(type(ngx.resp.get_headers) == "function", "get_headers should be a function")
assert(type(ngx.resp.set_header) == "function", "set_header should be a function")
assert(type(ngx.resp.clear_header) == "function", "clear_header should be a function")
`)
require.NoError(t, err)
}