lolly/internal/http3/adapter_test.go
xfy 2734b04d8f refactor: remove 16.8k lines of dead code across all internal packages
- Delete unused files: tempfile subsystem, matcher variants, server/internal
- Remove 200+ unused functions across proxy, ssl, lua, http2/3, stream, variable
- Fix proxy test type errors (backgroundRefresh ctx→Request)
- Move bench/tools mock backend into internal/testutil
- Remove corresponding test functions for all deleted code
2026-06-03 16:15:43 +08:00

599 lines
14 KiB
Go
Raw 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 http3 提供 HTTP/3 适配器功能的测试。
//
// 该文件测试 HTTP/3 适配器模块的各项功能,包括:
// - 适配器创建和初始化
// - HTTP 请求到 fasthttp 请求的转换
// - fasthttp 响应到 HTTP 响应的转换
// - 请求方法、URI、头部、请求体的转换
// - 完整请求响应流程
//
// 作者xfy
package http3
import (
"bytes"
"io"
"net/http"
"net/url"
"testing"
"github.com/valyala/fasthttp"
)
// TestNewAdapter 测试适配器创建
func TestNewAdapter(t *testing.T) {
adapter := NewAdapter()
if adapter == nil {
t.Fatal("Expected non-nil adapter")
}
}
// TestWrap 测试 Wrap 函数基本功能
func TestWrap(t *testing.T) {
adapter := NewAdapter()
// 创建一个简单的 fasthttp handler
handler := func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(200)
ctx.SetBodyString("Hello from fasthttp")
ctx.Response.Header.Set("Content-Type", "text/plain")
}
httpHandler := adapter.Wrap(handler)
if httpHandler == nil {
t.Error("Expected non-nil http.Handler")
}
}
// TestConvertRequest_Method 测试请求方法转换
func TestConvertRequest_Method(t *testing.T) {
adapter := NewAdapter()
tests := []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"}
for _, method := range tests {
t.Run(method, func(t *testing.T) {
req := &http.Request{
Method: method,
URL: &url.URL{Path: "/test"},
Host: "localhost",
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
if string(ctx.Method()) != method {
t.Errorf("Expected method %s, got %s", method, ctx.Method())
}
})
}
}
// TestConvertRequest_URI 测试 URI 转换
func TestConvertRequest_URI(t *testing.T) {
adapter := NewAdapter()
tests := []struct {
name string
path string
query string
expected string
}{
{
name: "simple path",
path: "/test",
query: "",
expected: "/test",
},
{
name: "path with query",
path: "/test",
query: "foo=bar",
expected: "/test?foo=bar",
},
{
name: "path with multiple query params",
path: "/api/users",
query: "id=1&name=test",
expected: "/api/users?id=1&name=test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: tt.path, RawQuery: tt.query},
Host: "localhost",
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
if string(ctx.RequestURI()) != tt.expected {
t.Errorf("Expected URI %s, got %s", tt.expected, ctx.RequestURI())
}
})
}
}
// TestConvertRequest_Headers 测试头部转换
func TestConvertRequest_Headers(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "example.com",
Header: http.Header{
"X-Custom-Header": []string{"value1", "value2"},
"Content-Type": []string{"application/json"},
"Accept": []string{"text/html"},
},
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
// 检查 Host
if string(ctx.Host()) != "example.com" {
t.Errorf("Expected Host example.com, got %s", ctx.Host())
}
// 检查头部
if string(ctx.Request.Header.Peek("Content-Type")) != "application/json" {
t.Errorf("Expected Content-Type application/json, got %s", ctx.Request.Header.Peek("Content-Type"))
}
if string(ctx.Request.Header.Peek("Accept")) != "text/html" {
t.Errorf("Expected Accept text/html, got %s", ctx.Request.Header.Peek("Accept"))
}
}
// TestConvertRequest_Body 测试请求体转换
func TestConvertRequest_Body(t *testing.T) {
adapter := NewAdapter()
bodyContent := []byte("test request body")
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/test"},
Host: "localhost",
Body: io.NopCloser(bytes.NewReader(bodyContent)),
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
if !bytes.Equal(ctx.Request.Body(), bodyContent) {
t.Errorf("Expected body %s, got %s", bodyContent, ctx.Request.Body())
}
}
// TestConvertRequest_RemoteAddr 测试远程地址转换
func TestConvertRequest_RemoteAddr(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
RemoteAddr: "192.168.1.1:8080",
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
remoteAddr := ctx.RemoteAddr()
if remoteAddr == nil {
t.Error("Expected non-nil remote address")
} else {
if remoteAddr.String() != "192.168.1.1:8080" {
t.Errorf("Expected remote addr 192.168.1.1:8080, got %s", remoteAddr.String())
}
}
}
// TestConvertResponse_Status 测试响应状态码转换
func TestConvertResponse_Status(t *testing.T) {
adapter := NewAdapter()
tests := []int{200, 201, 400, 404, 500}
for _, status := range tests {
t.Run(string(rune(status)), func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
ctx.SetStatusCode(status)
// 创建 mock ResponseWriter
rw := &mockResponseWriter{}
adapter.convertResponse(ctx, rw)
if rw.status != status {
t.Errorf("Expected status %d, got %d", status, rw.status)
}
})
}
}
// TestConvertResponse_DefaultStatus 测试默认状态码
func TestConvertResponse_DefaultStatus(t *testing.T) {
adapter := NewAdapter()
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
// 不设置状态码
rw := &mockResponseWriter{}
adapter.convertResponse(ctx, rw)
// 默认应该是 200
if rw.status != 200 {
t.Errorf("Expected default status 200, got %d", rw.status)
}
}
// TestConvertResponse_Headers 测试响应头部转换
func TestConvertResponse_Headers(t *testing.T) {
adapter := NewAdapter()
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
ctx.Response.Header.Set("Content-Type", "application/json")
ctx.Response.Header.Set("X-Custom", "value")
rw := &mockResponseWriter{}
adapter.convertResponse(ctx, rw)
if rw.header.Get("Content-Type") != "application/json" {
t.Errorf("Expected Content-Type application/json, got %s", rw.header.Get("Content-Type"))
}
if rw.header.Get("X-Custom") != "value" {
t.Errorf("Expected X-Custom value, got %s", rw.header.Get("X-Custom"))
}
}
// TestConvertResponse_Body 测试响应体转换
func TestConvertResponse_Body(t *testing.T) {
adapter := NewAdapter()
bodyContent := []byte("response body content")
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
ctx.SetBody(bodyContent)
rw := &mockResponseWriter{}
adapter.convertResponse(ctx, rw)
if !bytes.Equal(rw.body, bodyContent) {
t.Errorf("Expected body %s, got %s", bodyContent, rw.body)
}
}
// TestWrap_RoundTrip 完整流程测试
func TestWrap_RoundTrip(t *testing.T) {
adapter := NewAdapter()
// fasthttp handler
fastHandler := func(ctx *fasthttp.RequestCtx) {
// 检查请求
if string(ctx.Method()) != "POST" {
t.Errorf("Expected POST method, got %s", ctx.Method())
}
// 设置响应
ctx.SetStatusCode(201)
ctx.SetBodyString("Created")
ctx.Response.Header.Set("X-Response-Header", "test-value")
}
httpHandler := adapter.Wrap(fastHandler)
// 创建请求
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/create"},
Host: "localhost",
Header: http.Header{
"Content-Type": []string{"application/json"},
},
Body: io.NopCloser(bytes.NewReader([]byte("request data"))),
}
// 创建 mock ResponseWriter
rw := &mockResponseWriter{}
// 执行
httpHandler.ServeHTTP(rw, req)
// 检查响应
if rw.status != 201 {
t.Errorf("Expected status 201, got %d", rw.status)
}
if rw.header.Get("X-Response-Header") != "test-value" {
t.Errorf("Expected X-Response-Header test-value, got %s", rw.header.Get("X-Response-Header"))
}
if string(rw.body) != "Created" {
t.Errorf("Expected body 'Created', got %s", rw.body)
}
}
// mockResponseWriter 用于测试的 mock ResponseWriter
type mockResponseWriter struct {
header http.Header
body []byte
status int
}
func (m *mockResponseWriter) Header() http.Header {
if m.header == nil {
m.header = make(http.Header)
}
return m.header
}
func (m *mockResponseWriter) WriteHeader(statusCode int) {
m.status = statusCode
}
func (m *mockResponseWriter) Write(data []byte) (int, error) {
m.body = append(m.body, data...)
if m.status == 0 {
m.status = 200
}
return len(data), nil
}
// TestStreamRequestBody_LargeBody 测试大请求体的流式处理
func TestStreamRequestBody_LargeBody(t *testing.T) {
adapter := NewAdapter()
// 创建大于 64KB 的请求体
largeBody := make([]byte, 100*1024) // 100KB
for i := range largeBody {
largeBody[i] = byte(i % 256)
}
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/test"},
Host: "localhost",
Body: io.NopCloser(bytes.NewReader(largeBody)),
ContentLength: int64(len(largeBody)),
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
if !bytes.Equal(ctx.Request.Body(), largeBody) {
t.Errorf("Large body mismatch: expected %d bytes, got %d bytes", len(largeBody), len(ctx.Request.Body()))
}
}
// TestStreamRequestBody_UnknownContentLength 测试未知内容长度的请求体
func TestStreamRequestBody_UnknownContentLength(t *testing.T) {
adapter := NewAdapter()
bodyContent := []byte("test body with unknown length")
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/test"},
Host: "localhost",
Body: io.NopCloser(bytes.NewReader(bodyContent)),
ContentLength: -1, // 未知长度
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
if !bytes.Equal(ctx.Request.Body(), bodyContent) {
t.Errorf("Body mismatch: expected %s, got %s", bodyContent, ctx.Request.Body())
}
}
// TestStreamRequestBody_NoBody 测试无请求体的情况
func TestStreamRequestBody_NoBody(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
Body: nil,
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
if len(ctx.Request.Body()) != 0 {
t.Errorf("Expected empty body, got %d bytes", len(ctx.Request.Body()))
}
}
// TestStreamRequestBody_NoBodyConstant 测试 http.NoBody 的情况
func TestStreamRequestBody_NoBodyConstant(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
Body: http.NoBody,
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
if len(ctx.Request.Body()) != 0 {
t.Errorf("Expected empty body with http.NoBody, got %d bytes", len(ctx.Request.Body()))
}
}
// TestConvertRequest_EmptyRemoteAddr 测试空远程地址
func TestConvertRequest_EmptyRemoteAddr(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
RemoteAddr: "",
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
// 空远程地址不应该导致错误
// fasthttp 会设置默认值,所以只需验证不会 panic
}
// TestConvertRequest_InvalidRemoteAddr 测试无效远程地址
func TestConvertRequest_InvalidRemoteAddr(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
RemoteAddr: "invalid-address",
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
// 无效远程地址应该被忽略,不应导致错误
// ctx.RemoteAddr() 可能为 nil 或默认值
}
// TestWrap_TypeAssertionFailure 测试类型断言失败的情况
func TestWrap_TypeAssertionFailure(t *testing.T) {
adapter := NewAdapter()
// 创建一个会触发类型断言的 handler
handler := func(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(200)
ctx.SetBodyString("OK")
}
httpHandler := adapter.Wrap(handler)
// 执行请求
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
}
rw := &mockResponseWriter{}
httpHandler.ServeHTTP(rw, req)
// 应该正常处理
if rw.status != 200 {
t.Errorf("Expected status 200, got %d", rw.status)
}
}
// TestConvertResponse_EmptyBody 测试空响应体
func TestConvertResponse_EmptyBody(t *testing.T) {
adapter := NewAdapter()
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
ctx.SetStatusCode(204) // No Content
rw := &mockResponseWriter{}
adapter.convertResponse(ctx, rw)
if rw.status != 204 {
t.Errorf("Expected status 204, got %d", rw.status)
}
if len(rw.body) != 0 {
t.Errorf("Expected empty body, got %d bytes", len(rw.body))
}
}
// TestConvertRequest_Protocol 测试协议版本设置
func TestConvertRequest_Protocol(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "GET",
URL: &url.URL{Path: "/test"},
Host: "localhost",
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
adapter.convertRequest(req, ctx)
protocol := string(ctx.Request.Header.Protocol())
if protocol != "HTTP/3" {
t.Errorf("Expected protocol HTTP/3, got %s", protocol)
}
}
// errorReader 是一个会返回错误的 io.Reader
type errorReader struct{}
func (e *errorReader) Read(_ []byte) (n int, err error) {
return 0, io.ErrUnexpectedEOF
}
// TestStreamRequestBody_ReadError 测试读取请求体时的错误处理
func TestStreamRequestBody_ReadError(t *testing.T) {
adapter := NewAdapter()
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: "/test"},
Host: "localhost",
Body: io.NopCloser(&errorReader{}),
ContentLength: -1, // 强制使用流式读取
}
ctx := &fasthttp.RequestCtx{}
ctx.Init(&fasthttp.Request{}, nil, nil)
// 这不应该 panic应该优雅处理错误
adapter.convertRequest(req, ctx)
// 错误读取时body 应该为空
if len(ctx.Request.Body()) != 0 {
t.Errorf("Expected empty body on read error, got %d bytes", len(ctx.Request.Body()))
}
}