All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
主要改动: - 新增交互式环境配置脚本 (scripts/setup-env.sh) - 新增本地启动快捷脚本 (scripts/run-local.sh) - 新增环境变量模板文件 (.env.example) - 部署模式改版:使用嵌入式配置 + 环境变量覆盖 - 添加对象存储功能支持 - 改进 IoT 卡片导入任务 - 优化 OpenAPI 文档生成 - 删除旧的配置文件,改用嵌入式默认配置
626 lines
14 KiB
Go
626 lines
14 KiB
Go
package config
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestConfig_Validate tests configuration validation rules
|
|
func TestConfig_Validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config *Config
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "valid config",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
DB: 0,
|
|
PoolSize: 10,
|
|
MinIdleConns: 5,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
MaxBackups: 30,
|
|
MaxAge: 30,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
MaxBackups: 90,
|
|
MaxAge: 90,
|
|
},
|
|
},
|
|
Middleware: MiddlewareConfig{
|
|
RateLimiter: RateLimiterConfig{
|
|
Max: 100,
|
|
Expiration: 1 * time.Minute,
|
|
Storage: "memory",
|
|
},
|
|
},
|
|
JWT: JWTConfig{
|
|
TokenDuration: 24 * time.Hour,
|
|
AccessTokenTTL: 24 * time.Hour,
|
|
RefreshTokenTTL: 168 * time.Hour,
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty server address",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: "",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "server.address",
|
|
},
|
|
{
|
|
name: "read timeout too short",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 1 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "read_timeout",
|
|
},
|
|
{
|
|
name: "read timeout too long",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 400 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "read_timeout",
|
|
},
|
|
{
|
|
name: "write timeout out of range",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 1 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "write_timeout",
|
|
},
|
|
{
|
|
name: "shutdown timeout too short",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 5 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "shutdown_timeout",
|
|
},
|
|
{
|
|
name: "empty redis address",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "redis.address",
|
|
},
|
|
{
|
|
name: "invalid redis port - too high",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 99999,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "redis.port",
|
|
},
|
|
{
|
|
name: "invalid redis port - zero",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 0,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "redis.port",
|
|
},
|
|
{
|
|
name: "redis db out of range",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
DB: 20,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "redis.db",
|
|
},
|
|
{
|
|
name: "redis pool size too large",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 2000,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "pool_size",
|
|
},
|
|
{
|
|
name: "min idle conns exceeds pool size",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
MinIdleConns: 20,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "min_idle_conns",
|
|
},
|
|
{
|
|
name: "invalid log level",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "invalid",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "logging.level",
|
|
},
|
|
{
|
|
name: "empty app log filename",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "app_log.filename",
|
|
},
|
|
{
|
|
name: "app log max size out of range",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 2000,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "app_log.max_size",
|
|
},
|
|
{
|
|
name: "invalid rate limiter storage",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
Middleware: MiddlewareConfig{
|
|
RateLimiter: RateLimiterConfig{
|
|
Max: 100,
|
|
Storage: "invalid",
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "rate_limiter.storage",
|
|
},
|
|
{
|
|
name: "rate limiter max too high",
|
|
config: &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
Middleware: MiddlewareConfig{
|
|
RateLimiter: RateLimiterConfig{
|
|
Max: 20000,
|
|
Storage: "memory",
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
errMsg: "rate_limiter.max",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.config.Validate()
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if tt.wantErr && tt.errMsg != "" {
|
|
if err == nil {
|
|
t.Errorf("expected error containing %q, got nil", tt.errMsg)
|
|
} else if err.Error() == "" {
|
|
t.Errorf("expected error containing %q, got empty error", tt.errMsg)
|
|
}
|
|
// Note: We check that error message exists, not exact match
|
|
// This is because error messages might change slightly
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSet tests the Set function
|
|
func TestSet(t *testing.T) {
|
|
// Valid config
|
|
validCfg := &Config{
|
|
Server: ServerConfig{
|
|
Address: ":3000",
|
|
ReadTimeout: 10 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
ShutdownTimeout: 30 * time.Second,
|
|
},
|
|
Redis: RedisConfig{
|
|
Address: "localhost",
|
|
Port: 6379,
|
|
PoolSize: 10,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "info",
|
|
AppLog: LogRotationConfig{
|
|
Filename: "logs/app.log",
|
|
MaxSize: 100,
|
|
},
|
|
AccessLog: LogRotationConfig{
|
|
Filename: "logs/access.log",
|
|
MaxSize: 500,
|
|
},
|
|
},
|
|
JWT: JWTConfig{
|
|
TokenDuration: 24 * time.Hour,
|
|
AccessTokenTTL: 24 * time.Hour,
|
|
RefreshTokenTTL: 168 * time.Hour,
|
|
},
|
|
}
|
|
|
|
err := Set(validCfg)
|
|
if err != nil {
|
|
t.Errorf("Set() with valid config failed: %v", err)
|
|
}
|
|
|
|
// Verify it was set
|
|
got := Get()
|
|
if got.Server.Address != ":3000" {
|
|
t.Errorf("Get() after Set() returned wrong address: got %s, want :3000", got.Server.Address)
|
|
}
|
|
|
|
// Test with nil config
|
|
err = Set(nil)
|
|
if err == nil {
|
|
t.Error("Set(nil) should return error")
|
|
}
|
|
|
|
// Test with invalid config
|
|
invalidCfg := &Config{
|
|
Server: ServerConfig{
|
|
Address: "", // Empty address is invalid
|
|
},
|
|
}
|
|
|
|
err = Set(invalidCfg)
|
|
if err == nil {
|
|
t.Error("Set() with invalid config should return error")
|
|
}
|
|
}
|