test(http3): 完善 HTTP/3 服务器测试(覆盖率 46% → 93.1%)

补充 server_test.go 中未实现的测试用例:

新增测试:
- TestNewServer_TableDriven: 表驱动验证所有 NewServer 错误/成功路径
- TestNewServer_VerifyInternalFields: 验证服务器内部字段初始化
- TestStart_AlreadyRunning: 重复启动返回 "server already running"
- TestStart_InvalidListenAddress: 无效监听地址返回错误
- TestStart_Success: 绑定随机端口并验证运行状态
- TestStart_EmptyListenAddress: 空地址回退到 :443(无权限时 skip)
- TestStart_QUICConfigDefaults: 零值/自定义 MaxStreams、IdleTimeout、0RTT
- TestStart_MultipleStartsAndStops: start → stop → start 生命周期循环
- TestStop_NotRunning: 空闲服务器 stop 为空操作
- TestStop_Running: stop 正确设置 running = false
- TestStop_CalledMultipleTimes: 重复 stop 安全
- TestStartStop_Lifecycle: 完整生命周期状态断言
This commit is contained in:
xfy 2026-06-04 08:21:32 +08:00
parent f26a4a7949
commit 8bb88e8898

View File

@ -11,13 +11,57 @@
package http3
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
)
// newTestTLSConfig 创建用于测试的自签名 TLS 证书
func newTestTLSConfig(t *testing.T) *tls.Config {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
require.NoError(t, err)
cert := &tls.Certificate{
Certificate: [][]byte{certDER},
PrivateKey: key,
}
return &tls.Config{
Certificates: []tls.Certificate{*cert},
}
}
// newTestHandler 创建测试用的 fasthttp handler
func newTestHandler() fasthttp.RequestHandler {
return func(_ *fasthttp.RequestCtx) {}
}
// TestNewServer_NilConfig 测试空配置错误
func TestNewServer_NilConfig(t *testing.T) {
handler := func(_ *fasthttp.RequestCtx) {}
@ -120,6 +164,396 @@ func TestNewServer_Success(t *testing.T) {
}
}
// TestNewServer_TableDriven 使用表驱动测试各种配置组合
func TestNewServer_TableDriven(t *testing.T) {
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
tests := []struct {
name string
cfg *config.HTTP3Config
handler fasthttp.RequestHandler
tlsConfig *tls.Config
wantErr bool
errMsg string
}{
{
name: "空配置",
cfg: nil,
handler: handler,
tlsConfig: tlsConfig,
wantErr: true,
errMsg: "http3 config is nil",
},
{
name: "空handler",
cfg: &config.HTTP3Config{
Enabled: true,
Listen: ":443",
},
handler: nil,
tlsConfig: tlsConfig,
wantErr: true,
errMsg: "handler is nil",
},
{
name: "空TLS配置",
cfg: &config.HTTP3Config{
Enabled: true,
Listen: ":443",
},
handler: handler,
tlsConfig: nil,
wantErr: true,
errMsg: "tls config is required for HTTP/3",
},
{
name: "完整配置",
cfg: &config.HTTP3Config{
Enabled: true,
Listen: ":443",
MaxStreams: 200,
IdleTimeout: 30 * time.Second,
Enable0RTT: true,
},
handler: handler,
tlsConfig: tlsConfig,
wantErr: false,
},
{
name: "最小配置",
cfg: &config.HTTP3Config{
Enabled: true,
},
handler: handler,
tlsConfig: tlsConfig,
wantErr: false,
},
{
name: "禁用状态",
cfg: &config.HTTP3Config{
Enabled: false,
},
handler: handler,
tlsConfig: tlsConfig,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server, err := NewServer(tt.cfg, tt.handler, tt.tlsConfig)
if tt.wantErr {
require.Error(t, err)
assert.Equal(t, tt.errMsg, err.Error())
assert.Nil(t, server)
} else {
require.NoError(t, err)
require.NotNil(t, server)
assert.Equal(t, tt.cfg, server.config)
assert.NotNil(t, server.handler)
assert.NotNil(t, server.adapter)
assert.Equal(t, tt.tlsConfig, server.tlsConfig)
assert.False(t, server.running)
assert.Nil(t, server.listener)
assert.Nil(t, server.http3Server)
}
})
}
}
// TestNewServer_VerifyInternalFields 验证创建后内部字段的值
func TestNewServer_VerifyInternalFields(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":8443",
MaxStreams: 256,
IdleTimeout: 60 * time.Second,
Enable0RTT: true,
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
require.NotNil(t, server)
assert.Equal(t, cfg, server.config)
assert.Equal(t, tlsConfig, server.tlsConfig)
assert.NotNil(t, server.adapter)
assert.False(t, server.running)
assert.Nil(t, server.listener)
assert.Nil(t, server.http3Server)
}
// TestStart_AlreadyRunning 测试启动已运行的服务器
func TestStart_AlreadyRunning(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
require.NoError(t, server.Start())
t.Cleanup(func() { _ = server.Stop() })
err = server.Start()
require.Error(t, err)
assert.Equal(t, "server already running", err.Error())
}
// TestStart_InvalidListenAddress 测试无效监听地址
func TestStart_InvalidListenAddress(t *testing.T) {
tests := []struct {
name string
listen string
wantErr bool
}{
{
name: "无效地址格式",
listen: "not-a-valid-address:999999999",
wantErr: true,
},
{
name: "无效端口",
listen: ":999999999",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: tt.listen,
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
err = server.Start()
if tt.wantErr {
require.Error(t, err)
} else {
assert.NoError(t, err)
_ = server.Stop()
}
})
}
}
// TestStart_Success 测试成功启动服务器(使用随机端口)
func TestStart_Success(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
MaxStreams: 100,
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
require.NoError(t, server.Start())
t.Cleanup(func() { _ = server.Stop() })
assert.True(t, server.running)
assert.NotNil(t, server.listener)
assert.NotNil(t, server.http3Server)
}
// TestStart_EmptyListenAddress 测试空监听地址时使用默认值
func TestStart_EmptyListenAddress(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: "",
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
// 默认地址是 :443可能需要权限这里只验证逻辑不会 panic
// 如果绑定失败是因为权限,则跳过
err = server.Start()
if err != nil {
t.Skipf("无法绑定默认端口 :443可能需要权限: %v", err)
}
t.Cleanup(func() { _ = server.Stop() })
}
// TestStart_QUICConfigDefaults 测试 QUIC 配置默认值
func TestStart_QUICConfigDefaults(t *testing.T) {
tests := []struct {
name string
maxStreams int
idle time.Duration
enable0RTT bool
}{
{
name: "零值使用默认 MaxStreams",
maxStreams: 0,
idle: 0,
enable0RTT: false,
},
{
name: "自定义 MaxStreams",
maxStreams: 500,
idle: 60 * time.Second,
enable0RTT: true,
},
{
name: "最小 MaxStreams",
maxStreams: 1,
idle: 5 * time.Second,
enable0RTT: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
MaxStreams: tt.maxStreams,
IdleTimeout: tt.idle,
Enable0RTT: tt.enable0RTT,
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
require.NoError(t, server.Start())
t.Cleanup(func() { _ = server.Stop() })
assert.True(t, server.running)
assert.NotNil(t, server.listener)
})
}
}
// TestStart_MultipleStartsAndStops 测试多次启停
func TestStart_MultipleStartsAndStops(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
// 第一次启动
require.NoError(t, server.Start())
assert.True(t, server.running)
// 停止
require.NoError(t, server.Stop())
assert.False(t, server.running)
// 第二次启动(使用新端口,因为旧端口可能还在释放中)
server.config.Listen = ":0"
require.NoError(t, server.Start())
t.Cleanup(func() { _ = server.Stop() })
assert.True(t, server.running)
}
// TestStop_NotRunning 测试停止未运行的服务器
func TestStop_NotRunning(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
assert.False(t, server.running)
err = server.Stop()
require.NoError(t, err)
assert.False(t, server.running)
}
// TestStop_Running 测试停止正在运行的服务器
func TestStop_Running(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
require.NoError(t, server.Start())
assert.True(t, server.running)
require.NoError(t, server.Stop())
assert.False(t, server.running)
}
// TestStop_CalledMultipleTimes 测试多次停止不会报错
func TestStop_CalledMultipleTimes(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
require.NoError(t, server.Start())
require.NoError(t, server.Stop())
require.NoError(t, server.Stop())
require.NoError(t, server.Stop())
assert.False(t, server.running)
}
// TestStartStop_Lifecycle 测试完整的生命周期
func TestStartStop_Lifecycle(t *testing.T) {
cfg := &config.HTTP3Config{
Enabled: true,
Listen: ":0",
MaxStreams: 50,
IdleTimeout: 30 * time.Second,
Enable0RTT: false,
}
handler := newTestHandler()
tlsConfig := newTestTLSConfig(t)
server, err := NewServer(cfg, handler, tlsConfig)
require.NoError(t, err)
assert.False(t, server.running)
assert.Nil(t, server.listener)
assert.Nil(t, server.http3Server)
require.NoError(t, server.Start())
assert.True(t, server.running)
assert.NotNil(t, server.listener)
assert.NotNil(t, server.http3Server)
require.NoError(t, server.Stop())
assert.False(t, server.running)
}