diff --git a/internal/http3/server_test.go b/internal/http3/server_test.go index d57cda9..6f9b89a 100644 --- a/internal/http3/server_test.go +++ b/internal/http3/server_test.go @@ -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) +}