//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 } // 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} }