lolly/internal/server/start_integration_test.go

586 lines
12 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 server 提供 Server.Start() 的集成测试。
//
// 该文件使用 mock_backend 模拟上游服务,测试完整的服务器启动流程:
// - 服务器配置初始化
// - 代理路由注册
// - 静态文件服务
// - 中间件链构建
// - 请求处理和转发
//
// 主要用途:
//
// 验证服务器启动和请求处理的完整流程
//
// 作者xfy
package server
import (
"os"
"testing"
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
"rua.plus/lolly/internal/testutil"
)
// TestStart_Integration 测试完整的服务器启动和请求处理流程
func TestStart_Integration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
// 启动 mock 上游服务器
backendAddr, cleanup := testutil.SimpleMockBackend(
fasthttp.StatusOK,
[]byte(`{"message": "Hello from backend"}`),
)
defer cleanup()
// 使用随机端口避免冲突
serverAddr := "127.0.0.1:0"
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: serverAddr,
Proxy: []config.ProxyConfig{
{
Path: "/api",
Targets: []config.ProxyTarget{
{URL: "http://" + backendAddr, Weight: 1},
},
HealthCheck: config.HealthCheckConfig{},
},
},
},
},
}
s := New(cfg)
// 验证服务器初始化
if s == nil {
t.Fatal("Expected non-nil server")
}
// 测试初始状态
if s.running.Load() {
t.Error("Server should not be running initially")
}
// 测试 GetListeners 初始状态
listeners := s.GetListeners()
if listeners != nil {
t.Error("Listeners should be nil before Start")
}
}
// TestStart_WithSecurity 测试安全配置
func TestStart_WithSecurity(t *testing.T) {
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Security: config.SecurityConfig{
Access: config.AccessConfig{
Allow: []string{"127.0.0.1"},
Deny: []string{},
},
RateLimit: config.RateLimitConfig{
RequestRate: 100,
Burst: 200,
},
Headers: config.SecurityHeaders{
XFrameOptions: "DENY",
XContentTypeOptions: "nosniff",
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证安全配置
if len(s.config.Servers[0].Security.Access.Allow) != 1 {
t.Errorf("Expected 1 allowed IP, got %d", len(s.config.Servers[0].Security.Access.Allow))
}
}
// TestStart_WithRewrite 测试 URL 重写配置
func TestStart_WithRewrite(t *testing.T) {
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Rewrite: []config.RewriteRule{
{
Pattern: "/old/(.*)",
Replacement: "/new/$1",
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证重写配置
if len(s.config.Servers[0].Rewrite) != 1 {
t.Errorf("Expected 1 rewrite rule, got %d", len(s.config.Servers[0].Rewrite))
}
}
// TestStart_WithMonitoring 测试监控配置
func TestStart_WithMonitoring(t *testing.T) {
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
},
},
Monitoring: config.MonitoringConfig{
Status: config.StatusConfig{
Enabled: true,
Path: "/status",
Format: "json",
Allow: []string{"127.0.0.1"},
},
Pprof: config.PprofConfig{
Enabled: false,
Path: "/debug/pprof",
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证监控配置
if s.config.Monitoring.Status.Path != "/status" {
t.Errorf("Expected status path '/status', got '%s'", s.config.Monitoring.Status.Path)
}
}
// TestStart_WithErrorPage 测试错误页面配置
func TestStart_WithErrorPage(t *testing.T) {
// 创建临时错误页面文件
tempDir := t.TempDir()
errorPagePath := tempDir + "/404.html"
// 创建错误页面文件
content := []byte("<html><body>Not Found</body></html>")
if err := writeFile(errorPagePath, content); err != nil {
t.Fatalf("Failed to create error page file: %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: errorPagePath,
},
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证错误页面配置
if s.config.Servers[0].Security.ErrorPage.Pages == nil {
t.Error("Error page pages should not be nil")
}
}
// TestStart_WithLuaEnabled 测试 Lua 配置
func TestStart_WithLuaEnabled(t *testing.T) {
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Lua: &config.LuaMiddlewareConfig{
Enabled: true,
GlobalSettings: config.LuaGlobalSettings{
MaxConcurrentCoroutines: 100,
CoroutineTimeout: 30 * time.Second,
CodeCacheSize: 100,
MaxExecutionTime: 30 * time.Second,
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证 Lua 配置
if s.config.Servers[0].Lua == nil || !s.config.Servers[0].Lua.Enabled {
t.Error("Lua should be enabled")
}
}
// TestStart_WithMultipleProxies 测试多个代理配置
func TestStart_WithMultipleProxies(t *testing.T) {
// 启动多个 mock 上游服务器
backend1, cleanup1 := testutil.SimpleMockBackend(
fasthttp.StatusOK,
[]byte(`{"service": "api1"}`),
)
defer cleanup1()
backend2, cleanup2 := testutil.SimpleMockBackend(
fasthttp.StatusOK,
[]byte(`{"service": "api2"}`),
)
defer cleanup2()
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Proxy: []config.ProxyConfig{
{
Path: "/api1",
Targets: []config.ProxyTarget{
{URL: "http://" + backend1, Weight: 1},
},
},
{
Path: "/api2",
Targets: []config.ProxyTarget{
{URL: "http://" + backend2, Weight: 1},
},
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证代理配置
if len(s.config.Servers[0].Proxy) != 2 {
t.Errorf("Expected 2 proxies, got %d", len(s.config.Servers[0].Proxy))
}
}
// TestStart_EmptyConfig 测试空配置
func TestStart_EmptyConfig(t *testing.T) {
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 空配置应该能正常初始化
if s.config == nil {
t.Error("Config should not be nil")
}
}
// TestStart_WithAllFeatures 测试启用所有功能的配置
func TestStart_WithAllFeatures(t *testing.T) {
// 创建临时目录
tempDir := t.TempDir()
errorPagePath := tempDir + "/404.html"
writeFile(errorPagePath, []byte("<html><body>Not Found</body></html>"))
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Static: []config.StaticConfig{
{
Path: "/static",
Root: tempDir,
Index: []string{"index.html"},
},
},
Compression: config.CompressionConfig{
Type: "gzip",
Level: 6,
},
Security: config.SecurityConfig{
Access: config.AccessConfig{
Allow: []string{"127.0.0.1"},
},
RateLimit: config.RateLimitConfig{
RequestRate: 100,
Burst: 200,
},
Headers: config.SecurityHeaders{
XFrameOptions: "DENY",
},
ErrorPage: config.ErrorPageConfig{
Default: errorPagePath,
},
},
Rewrite: []config.RewriteRule{
{
Pattern: "/old/(.*)",
Replacement: "/new/$1",
},
},
},
},
Performance: config.PerformanceConfig{
GoroutinePool: config.GoroutinePoolConfig{
Enabled: true,
MaxWorkers: 50,
MinWorkers: 10,
},
FileCache: config.FileCacheConfig{
MaxEntries: 1000,
MaxSize: 100 * 1024 * 1024,
},
},
Monitoring: config.MonitoringConfig{
Status: config.StatusConfig{
Enabled: true,
Path: "/status",
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证所有配置
if !s.config.Performance.GoroutinePool.Enabled {
t.Error("GoroutinePool should be enabled")
}
if s.config.Servers[0].Compression.Type != "gzip" {
t.Error("Compression should be gzip")
}
}
// TestStart_ServerOptions 测试服务器配置选项
func TestStart_ServerOptions(t *testing.T) {
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxConnsPerIP: 100,
MaxRequestsPerConn: 1000,
ClientMaxBodySize: "10MB",
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证服务器选项
if s.config.Servers[0].ReadTimeout != 30*time.Second {
t.Errorf("Expected ReadTimeout 30s, got %v", s.config.Servers[0].ReadTimeout)
}
if s.config.Servers[0].MaxConnsPerIP != 100 {
t.Errorf("Expected MaxConnsPerIP 100, got %d", s.config.Servers[0].MaxConnsPerIP)
}
}
// TestStart_HealthCheckConfig 测试健康检查配置
func TestStart_HealthCheckConfig(t *testing.T) {
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:8081", Weight: 1},
},
HealthCheck: config.HealthCheckConfig{
Interval: 10 * time.Second,
Timeout: 5 * time.Second,
Path: "/health",
},
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证健康检查配置
if s.config.Servers[0].Proxy[0].HealthCheck.Path != "/health" {
t.Error("Health check path should be /health")
}
}
// TestStart_VHostMode 测试虚拟主机模式配置
func TestStart_VHostMode(t *testing.T) {
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Name: "api.example.com",
Listen: "127.0.0.1:0",
},
{
Name: "www.example.com",
Listen: "127.0.0.1:0",
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证虚拟主机配置
if !s.config.HasServers() {
t.Error("Should detect virtual hosts")
}
}
// TestStart_WithProxyBackendError 测试代理后端错误处理
func TestStart_WithProxyBackendError(t *testing.T) {
// 启动返回错误的 mock 服务器
backendAddr, cleanup := testutil.ErrorMockBackend(1.0, []byte(`{"error": "backend error"}`))
defer cleanup()
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Proxy: []config.ProxyConfig{
{
Path: "/api",
Targets: []config.ProxyTarget{
{URL: "http://" + backendAddr, Weight: 1},
},
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
// 验证代理配置
if len(s.config.Servers[0].Proxy) != 1 {
t.Errorf("Expected 1 proxy, got %d", len(s.config.Servers[0].Proxy))
}
}
// TestStart_WithDelayedBackend 测试延迟后端
func TestStart_WithDelayedBackend(t *testing.T) {
// 启动延迟的 mock 服务器
backendAddr, cleanup := testutil.DelayedMockBackend(
100*time.Millisecond,
fasthttp.StatusOK,
[]byte(`{"message": "delayed response"}`),
)
defer cleanup()
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Proxy: []config.ProxyConfig{
{
Path: "/api",
Targets: []config.ProxyTarget{
{URL: "http://" + backendAddr, Weight: 1},
},
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
}
// TestStart_WithRandomResponse 测试随机响应后端
func TestStart_WithRandomResponse(t *testing.T) {
// 启动随机响应的 mock 服务器
backendAddr, cleanup := testutil.StartMockFasthttpBackend(testutil.MockBackendConfig{
Mode: testutil.ModeRandomResponse,
StatusCode: fasthttp.StatusOK,
ResponseBody: []byte(`{"random": true}`),
})
defer cleanup()
cfg := &config.Config{
Servers: []config.ServerConfig{
{
Listen: "127.0.0.1:0",
Proxy: []config.ProxyConfig{
{
Path: "/api",
Targets: []config.ProxyTarget{
{URL: "http://" + backendAddr, Weight: 1},
},
},
},
},
},
}
s := New(cfg)
if s == nil {
t.Fatal("Expected non-nil server")
}
}
// writeFile 辅助函数:写入文件
func writeFile(path string, content []byte) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(content)
return err
}