670 lines
15 KiB
Go
670 lines
15 KiB
Go
// Package server 提供 startSingleMode 集成测试。
|
||
//
|
||
// 该文件测试 startSingleMode 函数的各种配置场景,
|
||
// 包括静态文件、代理、监控端点、TLS 等配置的实际启动。
|
||
//
|
||
// 作者:xfy
|
||
package server
|
||
|
||
import (
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"rua.plus/lolly/internal/config"
|
||
)
|
||
|
||
// TestStartSingleMode_Integration_WithStaticFiles 测试 startSingleMode 静态文件实际启动。
|
||
func TestStartSingleMode_Integration_WithStaticFiles(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
tempDir := t.TempDir()
|
||
// 创建一个测试文件
|
||
testFile := tempDir + "/index.html"
|
||
if err := os.WriteFile(testFile, []byte("<html>test</html>"), 0o644); err != nil {
|
||
t.Fatalf("failed to create test file: %v", err)
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Static: []config.StaticConfig{
|
||
{
|
||
Path: "/static",
|
||
Root: tempDir,
|
||
Index: []string{"index.html"},
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
// 在 goroutine 中启动服务器
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
// 等待服务器启动
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
// 服务器正常关闭会返回 nil 或 listener closed 错误
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
// 服务器仍在运行,已通过 GracefulStop 关闭
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithProxy 测试 startSingleMode 代理实际启动。
|
||
func TestStartSingleMode_Integration_WithProxy(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Proxy: []config.ProxyConfig{
|
||
{
|
||
Path: "/api",
|
||
Targets: []config.ProxyTarget{
|
||
{URL: "http://127.0.0.1:9999", Weight: 1}, // 不存在的后端
|
||
},
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithMonitoring 测试 startSingleMode 监控端点实际启动。
|
||
func TestStartSingleMode_Integration_WithMonitoring(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
}},
|
||
Monitoring: config.MonitoringConfig{
|
||
Status: config.StatusConfig{
|
||
Enabled: true,
|
||
Path: "/_status",
|
||
Allow: []string{"127.0.0.1"},
|
||
},
|
||
Pprof: config.PprofConfig{
|
||
Enabled: true,
|
||
Path: "/debug/pprof",
|
||
},
|
||
},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithCacheAPI 测试 startSingleMode 缓存 API 实际启动。
|
||
func TestStartSingleMode_Integration_WithCacheAPI(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
CacheAPI: &config.CacheAPIConfig{
|
||
Enabled: true,
|
||
Path: "/_cache/purge",
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithCompression 测试 startSingleMode 压缩配置实际启动。
|
||
func TestStartSingleMode_Integration_WithCompression(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Compression: config.CompressionConfig{
|
||
Type: "gzip",
|
||
Level: 6,
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithSecurity 测试 startSingleMode 安全配置实际启动。
|
||
func TestStartSingleMode_Integration_WithSecurity(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Security: config.SecurityConfig{
|
||
Access: config.AccessConfig{
|
||
Allow: []string{"127.0.0.1"},
|
||
},
|
||
RateLimit: config.RateLimitConfig{
|
||
RequestRate: 100,
|
||
Burst: 200,
|
||
},
|
||
Headers: config.SecurityHeaders{
|
||
XFrameOptions: "DENY",
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithRewrite 测试 startSingleMode 重写配置实际启动。
|
||
func TestStartSingleMode_Integration_WithRewrite(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Rewrite: []config.RewriteRule{
|
||
{Pattern: "^/old/(.*)$", Replacement: "/new/$1"},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithPerformance 测试 startSingleMode 性能配置实际启动。
|
||
func TestStartSingleMode_Integration_WithPerformance(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
}},
|
||
Performance: config.PerformanceConfig{
|
||
GoroutinePool: config.GoroutinePoolConfig{
|
||
Enabled: true,
|
||
MaxWorkers: 50,
|
||
MinWorkers: 5,
|
||
IdleTimeout: 10 * time.Second,
|
||
},
|
||
FileCache: config.FileCacheConfig{
|
||
MaxEntries: 1000,
|
||
MaxSize: 10 * 1024 * 1024,
|
||
},
|
||
},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithProxyLocationTypes 测试代理不同位置类型实际启动。
|
||
func TestStartSingleMode_Integration_WithProxyLocationTypes(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Proxy: []config.ProxyConfig{
|
||
{
|
||
Path: "/api/exact",
|
||
LocationType: "exact",
|
||
Targets: []config.ProxyTarget{{URL: "http://127.0.0.1:9999", Weight: 1}},
|
||
},
|
||
{
|
||
Path: "/api/priority",
|
||
LocationType: "prefix_priority",
|
||
Targets: []config.ProxyTarget{{URL: "http://127.0.0.1:9999", Weight: 1}},
|
||
},
|
||
{
|
||
Path: "^/api/regex/(.*)$",
|
||
LocationType: "regex",
|
||
Targets: []config.ProxyTarget{{URL: "http://127.0.0.1:9999", Weight: 1}},
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithStaticLocationTypes 测试静态文件不同位置类型实际启动。
|
||
func TestStartSingleMode_Integration_WithStaticLocationTypes(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
tempDir := t.TempDir()
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Static: []config.StaticConfig{
|
||
{
|
||
Path: "/static/exact",
|
||
Root: tempDir,
|
||
LocationType: "exact",
|
||
},
|
||
{
|
||
Path: "/static/priority",
|
||
Root: tempDir,
|
||
LocationType: "prefix_priority",
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithHealthCheck 测试代理健康检查实际启动。
|
||
func TestStartSingleMode_Integration_WithHealthCheck(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Proxy: []config.ProxyConfig{
|
||
{
|
||
Path: "/api",
|
||
Targets: []config.ProxyTarget{
|
||
{URL: "http://127.0.0.1:9999", Weight: 1},
|
||
},
|
||
HealthCheck: config.HealthCheckConfig{
|
||
Interval: 1 * time.Second,
|
||
Timeout: 500 * time.Millisecond,
|
||
Path: "/health",
|
||
},
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithMIMETypes 测试 MIME 类型配置实际启动。
|
||
func TestStartSingleMode_Integration_WithMIMETypes(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Types: config.TypesConfig{
|
||
Map: map[string]string{
|
||
".wasm": "application/wasm",
|
||
},
|
||
DefaultType: "application/octet-stream",
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithErrorPage 测试错误页面配置实际启动。
|
||
func TestStartSingleMode_Integration_WithErrorPage(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
tempDir := t.TempDir()
|
||
errorPage := tempDir + "/404.html"
|
||
if err := os.WriteFile(errorPage, []byte("<html>Not Found</html>"), 0o644); err != nil {
|
||
t.Fatalf("failed to create error page: %v", err)
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Security: config.SecurityConfig{
|
||
ErrorPage: config.ErrorPageConfig{
|
||
Pages: map[int]string{404: errorPage},
|
||
Default: errorPage,
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithConnLimiter 测试连接限制配置实际启动。
|
||
func TestStartSingleMode_Integration_WithConnLimiter(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Security: config.SecurityConfig{
|
||
RateLimit: config.RateLimitConfig{
|
||
ConnLimit: 10,
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// TestStartSingleMode_Integration_WithAuthRequest 测试外部认证配置实际启动。
|
||
func TestStartSingleMode_Integration_WithAuthRequest(t *testing.T) {
|
||
if testing.Short() {
|
||
t.Skip("skipping integration test in short mode")
|
||
}
|
||
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Security: config.SecurityConfig{
|
||
AuthRequest: config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "/auth/validate",
|
||
Timeout: 5 * time.Second,
|
||
},
|
||
},
|
||
}},
|
||
}
|
||
|
||
s := New(cfg)
|
||
|
||
errCh := make(chan error, 1)
|
||
go func() {
|
||
if err := s.Start(); err != nil {
|
||
errCh <- err
|
||
}
|
||
}()
|
||
|
||
waitForServerRunning(s, 2*time.Second)
|
||
_ = s.GracefulStop(2 * time.Second)
|
||
|
||
select {
|
||
case err := <-errCh:
|
||
if err != nil && !isExpectedServerErrorForIntegration(err) {
|
||
t.Errorf("unexpected server error: %v", err)
|
||
}
|
||
default:
|
||
}
|
||
}
|
||
|
||
// isExpectedServerErrorForIntegration 检查是否是预期的服务器关闭错误。
|
||
func isExpectedServerErrorForIntegration(err error) bool {
|
||
if err == nil {
|
||
return true
|
||
}
|
||
// fasthttp 服务器关闭时的常见错误
|
||
errStr := err.Error()
|
||
return strings.Contains(errStr, "closed") ||
|
||
strings.Contains(errStr, "use of closed") ||
|
||
strings.Contains(errStr, "listener closed")
|
||
}
|