xfy 0790c5a9e4 test(e2e/testutil): 扩展测试工具包
添加配置生成、常量定义、测试设置、SSL 和 WebSocket 工具函数。
重构 container.go 支持函数式选项模式配置容器。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 14:51:11 +08:00

585 lines
13 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.

//go:build e2e
// Package testutil 提供 E2E 测试的工具函数。
//
// 包含动态配置生成器,支持编程方式生成 YAML 配置文件。
//
// 作者xfy
package testutil
import (
"fmt"
"os"
"path/filepath"
"time"
"gopkg.in/yaml.v3"
"rua.plus/lolly/internal/config"
)
// ConfigBuilder 动态配置构建器。
//
// 支持编程方式生成 YAML 配置,用于 E2E 测试场景。
// 提供链式调用方式,方便组合不同配置。
//
// 使用示例:
//
// cfg := testutil.NewConfigBuilder().
// WithServer(":8080").
// WithProxy("/api/", targets).
// WithSSL(certPath, keyPath).
// Build()
type ConfigBuilder struct {
cfg *config.Config
}
// NewConfigBuilder 创建配置构建器。
func NewConfigBuilder() *ConfigBuilder {
return &ConfigBuilder{
cfg: &config.Config{
Servers: []config.ServerConfig{},
},
}
}
// WithServer 添加服务器配置。
//
// 参数:
// - listen: 监听地址,如 ":8080"
//
// 返回构建器以支持链式调用。
func (b *ConfigBuilder) WithServer(listen string) *ConfigBuilder {
b.cfg.Servers = append(b.cfg.Servers, config.ServerConfig{
Listen: listen,
})
return b
}
// WithServerConfig 添加完整服务器配置。
func (b *ConfigBuilder) WithServerConfig(server config.ServerConfig) *ConfigBuilder {
b.cfg.Servers = append(b.cfg.Servers, server)
return b
}
// ProxyTargetOption 代理目标选项。
type ProxyTargetOption func(*config.ProxyTarget)
// WithWeight 设置权重。
func WithWeight(weight int) ProxyTargetOption {
return func(t *config.ProxyTarget) {
t.Weight = weight
}
}
// WithMaxConns 设置最大连接数。
func WithMaxConns(maxConns int) ProxyTargetOption {
return func(t *config.ProxyTarget) {
t.MaxConns = maxConns
}
}
// WithMaxFails 设置最大失败次数。
func WithMaxFails(maxFails int, failTimeout time.Duration) ProxyTargetOption {
return func(t *config.ProxyTarget) {
t.MaxFails = maxFails
t.FailTimeout = failTimeout
}
}
// WithBackup 设置为备份服务器。
func WithBackup() ProxyTargetOption {
return func(t *config.ProxyTarget) {
t.Backup = true
}
}
// ProxyOption 代理配置选项。
type ProxyOption func(*config.ProxyConfig)
// ProxyConfig 代理配置类型别名。
type ProxyConfig = config.ProxyConfig
// WithLoadBalance 设置负载均衡算法。
func WithLoadBalance(algorithm string) ProxyOption {
return func(p *config.ProxyConfig) {
p.LoadBalance = algorithm
}
}
// WithHealthCheck 设置健康检查。
func WithHealthCheck(path string, interval, timeout time.Duration) ProxyOption {
return func(p *config.ProxyConfig) {
p.HealthCheck = config.HealthCheckConfig{
Path: path,
Interval: interval,
Timeout: timeout,
}
}
}
// WithProxyTimeout 设置代理超时。
func WithProxyTimeout(connect, read, write time.Duration) ProxyOption {
return func(p *config.ProxyConfig) {
p.Timeout = config.ProxyTimeout{
Connect: connect,
Read: read,
Write: write,
}
}
}
// WithProxyHeaders 设置代理头部。
func WithProxyHeaders(setRequest, setResponse map[string]string) ProxyOption {
return func(p *config.ProxyConfig) {
p.Headers = config.ProxyHeaders{
SetRequest: setRequest,
SetResponse: setResponse,
}
}
}
// WithProxyCache 设置代理缓存。
func WithProxyCache(maxAge time.Duration, cacheLock bool) ProxyOption {
return func(p *config.ProxyConfig) {
p.Cache = config.ProxyCacheConfig{
Enabled: true,
MaxAge: maxAge,
CacheLock: cacheLock,
}
}
}
// WithProxySSL 设置上游 SSL。
func WithProxySSL(serverName string, insecureSkipVerify bool) ProxyOption {
return func(p *config.ProxyConfig) {
p.ProxySSL = &config.ProxySSLConfig{
Enabled: true,
ServerName: serverName,
InsecureSkipVerify: insecureSkipVerify,
}
}
}
// WithProxyBuffering 设置代理缓冲。
func WithProxyBuffering(mode string, bufferSize int) ProxyOption {
return func(p *config.ProxyConfig) {
p.Buffering = &config.ProxyBufferingConfig{
Mode: mode,
BufferSize: bufferSize,
}
}
}
// WithProxyNextUpstream 设置故障转移。
func WithProxyNextUpstream(tries int, httpCodes []int) ProxyOption {
return func(p *config.ProxyConfig) {
p.NextUpstream = config.NextUpstreamConfig{
Tries: tries,
HTTPCodes: httpCodes,
}
}
}
// WithProxy 添加代理配置。
//
// 参数:
// - path: 代理路径前缀
// - urls: 后端 URL 列表
// - opts: 可选配置选项
//
// 返回构建器以支持链式调用。
func (b *ConfigBuilder) WithProxy(path string, urls []string, opts ...ProxyOption) *ConfigBuilder {
if len(b.cfg.Servers) == 0 {
b.WithServer(":8080")
}
targets := make([]config.ProxyTarget, len(urls))
for i, url := range urls {
targets[i] = config.ProxyTarget{
URL: url,
}
}
proxy := config.ProxyConfig{
Path: path,
Targets: targets,
}
for _, opt := range opts {
opt(&proxy)
}
// 添加到第一个服务器
b.cfg.Servers[0].Proxy = append(b.cfg.Servers[0].Proxy, proxy)
return b
}
// WithProxyTargets 添加带选项的代理配置。
//
// targetOptsPerTarget 是每个目标的选项列表(索引对应 urls
// opts 是代理级别的选项。
func (b *ConfigBuilder) WithProxyTargets(path string, urls []string, targetOptsPerTarget [][]ProxyTargetOption, opts ...ProxyOption) *ConfigBuilder {
if len(b.cfg.Servers) == 0 {
b.WithServer(":8080")
}
targets := make([]config.ProxyTarget, len(urls))
for i, url := range urls {
targets[i] = config.ProxyTarget{
URL: url,
}
// 应用该目标的选项
if i < len(targetOptsPerTarget) {
for _, opt := range targetOptsPerTarget[i] {
opt(&targets[i])
}
}
}
proxy := config.ProxyConfig{
Path: path,
Targets: targets,
}
for _, opt := range opts {
opt(&proxy)
}
b.cfg.Servers[0].Proxy = append(b.cfg.Servers[0].Proxy, proxy)
return b
}
// SSLOption SSL 配置选项。
type SSLOption func(*config.SSLConfig)
// WithHTTP2 启用 HTTP/2。
func WithHTTP2(enabled bool, maxConcurrentStreams int) SSLOption {
return func(s *config.SSLConfig) {
s.HTTP2 = config.HTTP2Config{
Enabled: enabled,
MaxConcurrentStreams: maxConcurrentStreams,
}
}
}
// WithTLSProtocols 设置 TLS 协议版本。
func WithTLSProtocols(protocols []string) SSLOption {
return func(s *config.SSLConfig) {
s.Protocols = protocols
}
}
// WithSessionTickets 启用 Session Tickets。
func WithSessionTickets(enabled bool) SSLOption {
return func(s *config.SSLConfig) {
s.SessionTickets = config.SessionTicketsConfig{
Enabled: enabled,
}
}
}
// WithHSTS 配置 HSTS。
func WithHSTS(maxAge int, includeSubDomains bool) SSLOption {
return func(s *config.SSLConfig) {
s.HSTS = config.HSTSConfig{
MaxAge: maxAge,
IncludeSubDomains: includeSubDomains,
}
}
}
// WithSSL 配置 SSL/TLS。
//
// 参数:
// - cert: 证书文件路径
// - key: 私钥文件路径
// - opts: 可选 SSL 配置
//
// 返回构建器以支持链式调用。
func (b *ConfigBuilder) WithSSL(cert, key string, opts ...SSLOption) *ConfigBuilder {
if len(b.cfg.Servers) == 0 {
b.WithServer(":8443")
}
ssl := config.SSLConfig{
Cert: cert,
Key: key,
}
for _, opt := range opts {
opt(&ssl)
}
b.cfg.Servers[0].SSL = ssl
return b
}
// StaticOption 静态文件配置选项。
type StaticOption func(*config.StaticConfig)
// WithIndex 设置索引文件。
func WithIndex(index []string) StaticOption {
return func(s *config.StaticConfig) {
s.Index = index
}
}
// WithTryFiles 设置 try_files。
func WithTryFiles(tryFiles []string) StaticOption {
return func(s *config.StaticConfig) {
s.TryFiles = tryFiles
}
}
// WithStatic 添加静态文件配置。
func (b *ConfigBuilder) WithStatic(path, root string, opts ...StaticOption) *ConfigBuilder {
if len(b.cfg.Servers) == 0 {
b.WithServer(":8080")
}
static := config.StaticConfig{
Path: path,
Root: root,
}
for _, opt := range opts {
opt(&static)
}
b.cfg.Servers[0].Static = append(b.cfg.Servers[0].Static, static)
return b
}
// SecurityOption 安全配置选项。
type SecurityOption func(*config.SecurityConfig)
// WithRateLimit 设置速率限制。
func WithRateLimit(requestRate, burst int) SecurityOption {
return func(s *config.SecurityConfig) {
s.RateLimit = config.RateLimitConfig{
RequestRate: requestRate,
Burst: burst,
}
}
}
// WithAccessControl 设置访问控制。
func WithAccessControl(allow, deny []string, defaultAction string) SecurityOption {
return func(s *config.SecurityConfig) {
s.Access = config.AccessConfig{
Allow: allow,
Deny: deny,
Default: defaultAction,
}
}
}
// WithBasicAuth 设置 Basic 认证。
func WithBasicAuth(users []config.User) SecurityOption {
return func(s *config.SecurityConfig) {
s.Auth = config.AuthConfig{
Type: "basic",
Users: users,
}
}
}
// WithSecurity 配置安全选项。
func (b *ConfigBuilder) WithSecurity(opts ...SecurityOption) *ConfigBuilder {
if len(b.cfg.Servers) == 0 {
b.WithServer(":8080")
}
for _, opt := range opts {
opt(&b.cfg.Servers[0].Security)
}
return b
}
// CompressionOption 压缩配置选项。
type CompressionOption func(*config.CompressionConfig)
// WithCompressionType 设置压缩类型。
func WithCompressionType(typ string) CompressionOption {
return func(c *config.CompressionConfig) {
c.Type = typ
}
}
// WithCompressionLevel 设置压缩级别。
func WithCompressionLevel(level int) CompressionOption {
return func(c *config.CompressionConfig) {
c.Level = level
}
}
// WithCompressionMinSize 设置最小压缩大小。
func WithCompressionMinSize(minSize int) CompressionOption {
return func(c *config.CompressionConfig) {
c.MinSize = minSize
}
}
// WithCompression 配置压缩。
func (b *ConfigBuilder) WithCompression(opts ...CompressionOption) *ConfigBuilder {
if len(b.cfg.Servers) == 0 {
b.WithServer(":8080")
}
for _, opt := range opts {
opt(&b.cfg.Servers[0].Compression)
}
return b
}
// RewriteOption 重写规则选项。
type RewriteOption func(*config.RewriteRule)
// WithRewriteFlag 设置重写标志。
func WithRewriteFlag(flag string) RewriteOption {
return func(r *config.RewriteRule) {
r.Flag = flag
}
}
// WithRewrite 添加 URL 重写规则。
func (b *ConfigBuilder) WithRewrite(pattern, replacement string, opts ...RewriteOption) *ConfigBuilder {
if len(b.cfg.Servers) == 0 {
b.WithServer(":8080")
}
rule := config.RewriteRule{
Pattern: pattern,
Replacement: replacement,
}
for _, opt := range opts {
opt(&rule)
}
b.cfg.Servers[0].Rewrite = append(b.cfg.Servers[0].Rewrite, rule)
return b
}
// WithCachePath 配置缓存路径。
func (b *ConfigBuilder) WithCachePath(path string, maxSize int64) *ConfigBuilder {
b.cfg.CachePath = &config.ProxyCachePathConfig{
Path: path,
MaxSize: maxSize,
}
return b
}
// WithResolver 配置 DNS 解析器。
func (b *ConfigBuilder) WithResolver(addresses []string, valid, timeout time.Duration) *ConfigBuilder {
b.cfg.Resolver = config.ResolverConfig{
Enabled: true,
Addresses: addresses,
Valid: valid,
Timeout: timeout,
}
return b
}
// WithLogging 配置日志。
func (b *ConfigBuilder) WithLogging(format string) *ConfigBuilder {
b.cfg.Logging = config.LoggingConfig{
Format: format,
}
return b
}
// WithShutdown 配置关闭超时。
func (b *ConfigBuilder) WithShutdown(graceful, fast time.Duration) *ConfigBuilder {
b.cfg.Shutdown = config.ShutdownConfig{
GracefulTimeout: graceful,
FastTimeout: fast,
}
return b
}
// Build 生成 YAML 配置字符串。
//
// 返回 YAML 格式的配置字符串。
func (b *ConfigBuilder) Build() (string, error) {
data, err := yaml.Marshal(b.cfg)
if err != nil {
return "", fmt.Errorf("序列化配置失败: %w", err)
}
return string(data), nil
}
// WriteTemp 写入临时文件。
//
// 创建临时目录并写入配置文件,返回文件路径。
// 调用者负责清理临时目录。
func (b *ConfigBuilder) WriteTemp() (string, error) {
yamlStr, err := b.Build()
if err != nil {
return "", err
}
// 创建临时目录
tmpDir, err := os.MkdirTemp("", "lolly-e2e-*")
if err != nil {
return "", fmt.Errorf("创建临时目录失败: %w", err)
}
// 写入配置文件
configPath := filepath.Join(tmpDir, "lolly.yaml")
if err := os.WriteFile(configPath, []byte(yamlStr), 0o644); err != nil {
os.RemoveAll(tmpDir)
return "", fmt.Errorf("写入配置文件失败: %w", err)
}
return configPath, nil
}
// WriteTo 写入指定目录。
func (b *ConfigBuilder) WriteTo(dir string) (string, error) {
yamlStr, err := b.Build()
if err != nil {
return "", err
}
// 确保目录存在
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", fmt.Errorf("创建目录失败: %w", err)
}
configPath := filepath.Join(dir, "lolly.yaml")
if err := os.WriteFile(configPath, []byte(yamlStr), 0o644); err != nil {
return "", fmt.Errorf("写入配置文件失败: %w", err)
}
return configPath, nil
}
// GetConfig 返回配置对象。
func (b *ConfigBuilder) GetConfig() *config.Config {
return b.cfg
}
// Reset 重置构建器。
func (b *ConfigBuilder) Reset() *ConfigBuilder {
b.cfg = &config.Config{
Servers: []config.ServerConfig{},
}
return b
}
// Clone 克隆构建器。
func (b *ConfigBuilder) Clone() *ConfigBuilder {
data, err := yaml.Marshal(b.cfg)
if err != nil {
return NewConfigBuilder()
}
var newCfg config.Config
if err := yaml.Unmarshal(data, &newCfg); err != nil {
return NewConfigBuilder()
}
return &ConfigBuilder{cfg: &newCfg}
}