feat: 添加环境变量管理工具和部署配置改版
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m33s
主要改动: - 新增交互式环境配置脚本 (scripts/setup-env.sh) - 新增本地启动快捷脚本 (scripts/run-local.sh) - 新增环境变量模板文件 (.env.example) - 部署模式改版:使用嵌入式配置 + 环境变量覆盖 - 添加对象存储功能支持 - 改进 IoT 卡片导入任务 - 优化 OpenAPI 文档生成 - 删除旧的配置文件,改用嵌入式默认配置
This commit is contained in:
@@ -2,650 +2,219 @@ package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// TestLoad tests the config loading functionality
|
||||
func TestLoad(t *testing.T) {
|
||||
func TestLoad_EmbeddedConfig(t *testing.T) {
|
||||
clearEnvVars(t)
|
||||
setRequiredEnvVars(t)
|
||||
defer clearEnvVars(t)
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() 失败: %v", err)
|
||||
}
|
||||
|
||||
if cfg.Server.Address != ":3000" {
|
||||
t.Errorf("server.address 期望 :3000, 实际 %s", cfg.Server.Address)
|
||||
}
|
||||
if cfg.Server.ReadTimeout != 30*time.Second {
|
||||
t.Errorf("server.read_timeout 期望 30s, 实际 %v", cfg.Server.ReadTimeout)
|
||||
}
|
||||
if cfg.Logging.Level != "info" {
|
||||
t.Errorf("logging.level 期望 info, 实际 %s", cfg.Logging.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_EnvOverride(t *testing.T) {
|
||||
clearEnvVars(t)
|
||||
setRequiredEnvVars(t)
|
||||
defer clearEnvVars(t)
|
||||
|
||||
os.Setenv("JUNHONG_SERVER_ADDRESS", ":8080")
|
||||
os.Setenv("JUNHONG_LOGGING_LEVEL", "debug")
|
||||
defer func() {
|
||||
os.Unsetenv("JUNHONG_SERVER_ADDRESS")
|
||||
os.Unsetenv("JUNHONG_LOGGING_LEVEL")
|
||||
}()
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() 失败: %v", err)
|
||||
}
|
||||
|
||||
if cfg.Server.Address != ":8080" {
|
||||
t.Errorf("server.address 期望 :8080, 实际 %s", cfg.Server.Address)
|
||||
}
|
||||
if cfg.Logging.Level != "debug" {
|
||||
t.Errorf("logging.level 期望 debug, 实际 %s", cfg.Logging.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_MissingRequired(t *testing.T) {
|
||||
clearEnvVars(t)
|
||||
defer clearEnvVars(t)
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
t.Fatal("Load() 缺少必填配置时应返回错误")
|
||||
}
|
||||
|
||||
expectedFields := []string{"database.host", "database.user", "database.password", "database.dbname", "redis.address", "jwt.secret_key"}
|
||||
for _, field := range expectedFields {
|
||||
if !containsString(err.Error(), field) {
|
||||
t.Errorf("错误信息应包含 %q, 实际: %s", field, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_PartialRequired(t *testing.T) {
|
||||
clearEnvVars(t)
|
||||
defer clearEnvVars(t)
|
||||
|
||||
os.Setenv("JUNHONG_DATABASE_HOST", "localhost")
|
||||
os.Setenv("JUNHONG_DATABASE_USER", "user")
|
||||
|
||||
_, err := Load()
|
||||
if err == nil {
|
||||
t.Fatal("Load() 部分必填配置缺失时应返回错误")
|
||||
}
|
||||
|
||||
if containsString(err.Error(), "database.host") {
|
||||
t.Error("database.host 已设置,不应在错误信息中")
|
||||
}
|
||||
if containsString(err.Error(), "database.user") {
|
||||
t.Error("database.user 已设置,不应在错误信息中")
|
||||
}
|
||||
if !containsString(err.Error(), "database.password") {
|
||||
t.Error("database.password 未设置,应在错误信息中")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_GlobalConfig(t *testing.T) {
|
||||
clearEnvVars(t)
|
||||
setRequiredEnvVars(t)
|
||||
defer clearEnvVars(t)
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load() 失败: %v", err)
|
||||
}
|
||||
|
||||
globalCfg := Get()
|
||||
if globalCfg == nil {
|
||||
t.Fatal("Get() 返回 nil")
|
||||
}
|
||||
|
||||
if globalCfg.Server.Address != cfg.Server.Address {
|
||||
t.Errorf("全局配置与返回配置不一致")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRequired(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupEnv func()
|
||||
cleanupEnv func()
|
||||
createConfig func(t *testing.T) string
|
||||
wantErr bool
|
||||
validateFunc func(t *testing.T, cfg *Config)
|
||||
name string
|
||||
cfg *Config
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid default config",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv(constants.EnvConfigPath, "")
|
||||
_ = os.Setenv(constants.EnvConfigEnv, "")
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv(constants.EnvConfigPath)
|
||||
_ = os.Unsetenv(constants.EnvConfigEnv)
|
||||
},
|
||||
createConfig: func(t *testing.T) string {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||
content := `
|
||||
server:
|
||||
address: ":3000"
|
||||
read_timeout: "10s"
|
||||
write_timeout: "10s"
|
||||
shutdown_timeout: "30s"
|
||||
prefork: false
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 6379
|
||||
password: ""
|
||||
db: 0
|
||||
pool_size: 10
|
||||
min_idle_conns: 5
|
||||
dial_timeout: "5s"
|
||||
read_timeout: "3s"
|
||||
write_timeout: "3s"
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
development: false
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: true
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 500
|
||||
max_backups: 90
|
||||
max_age: 90
|
||||
compress: true
|
||||
|
||||
middleware:
|
||||
enable_auth: true
|
||||
enable_rate_limiter: false
|
||||
rate_limiter:
|
||||
max: 100
|
||||
expiration: "1m"
|
||||
storage: "memory"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to create config file: %v", err)
|
||||
}
|
||||
// Set as default config path
|
||||
_ = os.Setenv(constants.EnvConfigPath, configFile)
|
||||
return configFile
|
||||
name: "all required set",
|
||||
cfg: &Config{
|
||||
Database: DatabaseConfig{
|
||||
Host: "localhost",
|
||||
User: "user",
|
||||
Password: "pass",
|
||||
DBName: "db",
|
||||
},
|
||||
Redis: RedisConfig{Address: "localhost"},
|
||||
JWT: JWTConfig{SecretKey: "12345678901234567890123456789012"},
|
||||
},
|
||||
wantErr: false,
|
||||
validateFunc: func(t *testing.T, cfg *Config) {
|
||||
if cfg.Server.Address != ":3000" {
|
||||
t.Errorf("expected server.address :3000, got %s", cfg.Server.Address)
|
||||
}
|
||||
if cfg.Server.ReadTimeout != 10*time.Second {
|
||||
t.Errorf("expected read_timeout 10s, got %v", cfg.Server.ReadTimeout)
|
||||
}
|
||||
if cfg.Redis.Address != "localhost" {
|
||||
t.Errorf("expected redis.address localhost, got %s", cfg.Redis.Address)
|
||||
}
|
||||
if cfg.Redis.Port != 6379 {
|
||||
t.Errorf("expected redis.port 6379, got %d", cfg.Redis.Port)
|
||||
}
|
||||
if cfg.Redis.PoolSize != 10 {
|
||||
t.Errorf("expected redis.pool_size 10, got %d", cfg.Redis.PoolSize)
|
||||
}
|
||||
if cfg.Logging.Level != "info" {
|
||||
t.Errorf("expected logging.level info, got %s", cfg.Logging.Level)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "environment-specific config (dev)",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv(constants.EnvConfigEnv, "dev")
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv(constants.EnvConfigEnv)
|
||||
_ = os.Unsetenv(constants.EnvConfigPath)
|
||||
},
|
||||
createConfig: func(t *testing.T) string {
|
||||
t.Helper()
|
||||
// Create configs directory in temp
|
||||
tmpDir := t.TempDir()
|
||||
configsDir := filepath.Join(tmpDir, "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
// Create dev config
|
||||
devConfigFile := filepath.Join(configsDir, "config.dev.yaml")
|
||||
content := `
|
||||
server:
|
||||
address: ":8080"
|
||||
read_timeout: "15s"
|
||||
write_timeout: "15s"
|
||||
shutdown_timeout: "30s"
|
||||
prefork: false
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 6379
|
||||
password: ""
|
||||
db: 1
|
||||
pool_size: 5
|
||||
min_idle_conns: 2
|
||||
dial_timeout: "5s"
|
||||
read_timeout: "3s"
|
||||
write_timeout: "3s"
|
||||
|
||||
logging:
|
||||
level: "debug"
|
||||
development: true
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 50
|
||||
max_backups: 10
|
||||
max_age: 7
|
||||
compress: false
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: false
|
||||
|
||||
middleware:
|
||||
enable_rate_limiter: false
|
||||
rate_limiter:
|
||||
max: 50
|
||||
expiration: "1m"
|
||||
storage: "memory"
|
||||
`
|
||||
if err := os.WriteFile(devConfigFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to create dev config file: %v", err)
|
||||
}
|
||||
|
||||
// Change to tmpDir so relative path works
|
||||
originalWd, _ := os.Getwd()
|
||||
_ = os.Chdir(tmpDir)
|
||||
t.Cleanup(func() { _ = os.Chdir(originalWd) })
|
||||
|
||||
return devConfigFile
|
||||
},
|
||||
wantErr: false,
|
||||
validateFunc: func(t *testing.T, cfg *Config) {
|
||||
if cfg.Server.Address != ":8080" {
|
||||
t.Errorf("expected server.address :8080, got %s", cfg.Server.Address)
|
||||
}
|
||||
if cfg.Redis.DB != 1 {
|
||||
t.Errorf("expected redis.db 1, got %d", cfg.Redis.DB)
|
||||
}
|
||||
if cfg.Logging.Level != "debug" {
|
||||
t.Errorf("expected logging.level debug, got %s", cfg.Logging.Level)
|
||||
}
|
||||
name: "missing database host",
|
||||
cfg: &Config{
|
||||
Database: DatabaseConfig{
|
||||
User: "user",
|
||||
Password: "pass",
|
||||
DBName: "db",
|
||||
},
|
||||
Redis: RedisConfig{Address: "localhost"},
|
||||
JWT: JWTConfig{SecretKey: "12345678901234567890123456789012"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid YAML syntax",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv(constants.EnvConfigPath, "")
|
||||
_ = os.Setenv(constants.EnvConfigEnv, "")
|
||||
name: "missing redis address",
|
||||
cfg: &Config{
|
||||
Database: DatabaseConfig{
|
||||
Host: "localhost",
|
||||
User: "user",
|
||||
Password: "pass",
|
||||
DBName: "db",
|
||||
},
|
||||
Redis: RedisConfig{},
|
||||
JWT: JWTConfig{SecretKey: "12345678901234567890123456789012"},
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv(constants.EnvConfigPath)
|
||||
_ = os.Unsetenv(constants.EnvConfigEnv)
|
||||
},
|
||||
createConfig: func(t *testing.T) string {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||
content := `
|
||||
server:
|
||||
address: ":3000"
|
||||
invalid yaml syntax here!!!
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to create config file: %v", err)
|
||||
}
|
||||
_ = os.Setenv(constants.EnvConfigPath, configFile)
|
||||
return configFile
|
||||
},
|
||||
wantErr: true,
|
||||
validateFunc: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "validation error - invalid server address",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv(constants.EnvConfigPath, "")
|
||||
name: "missing jwt secret",
|
||||
cfg: &Config{
|
||||
Database: DatabaseConfig{
|
||||
Host: "localhost",
|
||||
User: "user",
|
||||
Password: "pass",
|
||||
DBName: "db",
|
||||
},
|
||||
Redis: RedisConfig{Address: "localhost"},
|
||||
JWT: JWTConfig{},
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv(constants.EnvConfigPath)
|
||||
},
|
||||
createConfig: func(t *testing.T) string {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||
content := `
|
||||
server:
|
||||
address: ""
|
||||
read_timeout: "10s"
|
||||
write_timeout: "10s"
|
||||
shutdown_timeout: "30s"
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 6379
|
||||
db: 0
|
||||
pool_size: 10
|
||||
min_idle_conns: 5
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: true
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 500
|
||||
max_backups: 90
|
||||
max_age: 90
|
||||
compress: true
|
||||
|
||||
middleware:
|
||||
enable_auth: true
|
||||
rate_limiter:
|
||||
max: 100
|
||||
expiration: "1m"
|
||||
storage: "memory"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to create config file: %v", err)
|
||||
}
|
||||
_ = os.Setenv(constants.EnvConfigPath, configFile)
|
||||
return configFile
|
||||
},
|
||||
wantErr: true,
|
||||
validateFunc: nil,
|
||||
},
|
||||
{
|
||||
name: "validation error - timeout out of range",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv(constants.EnvConfigPath, "")
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv(constants.EnvConfigPath)
|
||||
},
|
||||
createConfig: func(t *testing.T) string {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||
content := `
|
||||
server:
|
||||
address: ":3000"
|
||||
read_timeout: "1s"
|
||||
write_timeout: "10s"
|
||||
shutdown_timeout: "30s"
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 6379
|
||||
db: 0
|
||||
pool_size: 10
|
||||
min_idle_conns: 5
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: true
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 500
|
||||
max_backups: 90
|
||||
max_age: 90
|
||||
compress: true
|
||||
|
||||
middleware:
|
||||
enable_auth: true
|
||||
rate_limiter:
|
||||
max: 100
|
||||
expiration: "1m"
|
||||
storage: "memory"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to create config file: %v", err)
|
||||
}
|
||||
_ = os.Setenv(constants.EnvConfigPath, configFile)
|
||||
return configFile
|
||||
},
|
||||
wantErr: true,
|
||||
validateFunc: nil,
|
||||
},
|
||||
{
|
||||
name: "validation error - invalid redis port",
|
||||
setupEnv: func() {
|
||||
_ = os.Setenv(constants.EnvConfigPath, "")
|
||||
},
|
||||
cleanupEnv: func() {
|
||||
_ = os.Unsetenv(constants.EnvConfigPath)
|
||||
},
|
||||
createConfig: func(t *testing.T) string {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||
content := `
|
||||
server:
|
||||
address: ":3000"
|
||||
read_timeout: "10s"
|
||||
write_timeout: "10s"
|
||||
shutdown_timeout: "30s"
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 99999
|
||||
db: 0
|
||||
pool_size: 10
|
||||
min_idle_conns: 5
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: true
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 500
|
||||
max_backups: 90
|
||||
max_age: 90
|
||||
compress: true
|
||||
|
||||
middleware:
|
||||
enable_auth: true
|
||||
rate_limiter:
|
||||
max: 100
|
||||
expiration: "1m"
|
||||
storage: "memory"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to create config file: %v", err)
|
||||
}
|
||||
_ = os.Setenv(constants.EnvConfigPath, configFile)
|
||||
return configFile
|
||||
},
|
||||
wantErr: true,
|
||||
validateFunc: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Reset viper for each test
|
||||
viper.Reset()
|
||||
|
||||
// Setup environment
|
||||
if tt.setupEnv != nil {
|
||||
tt.setupEnv()
|
||||
}
|
||||
|
||||
// Create config file
|
||||
if tt.createConfig != nil {
|
||||
tt.createConfig(t)
|
||||
}
|
||||
|
||||
// Cleanup after test
|
||||
if tt.cleanupEnv != nil {
|
||||
defer tt.cleanupEnv()
|
||||
}
|
||||
|
||||
// Load config
|
||||
cfg, err := Load()
|
||||
|
||||
// Check error expectation
|
||||
err := tt.cfg.ValidateRequired()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate config if no error expected
|
||||
if !tt.wantErr && tt.validateFunc != nil {
|
||||
tt.validateFunc(t, cfg)
|
||||
t.Errorf("ValidateRequired() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestReload tests the config reload functionality
|
||||
func TestReload(t *testing.T) {
|
||||
// Reset viper
|
||||
viper.Reset()
|
||||
func setRequiredEnvVars(t *testing.T) {
|
||||
t.Helper()
|
||||
os.Setenv("JUNHONG_DATABASE_HOST", "localhost")
|
||||
os.Setenv("JUNHONG_DATABASE_USER", "testuser")
|
||||
os.Setenv("JUNHONG_DATABASE_PASSWORD", "testpass")
|
||||
os.Setenv("JUNHONG_DATABASE_DBNAME", "testdb")
|
||||
os.Setenv("JUNHONG_REDIS_ADDRESS", "localhost")
|
||||
os.Setenv("JUNHONG_JWT_SECRET_KEY", "12345678901234567890123456789012")
|
||||
}
|
||||
|
||||
// Create temp config file
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||
|
||||
// Initial config
|
||||
initialContent := `
|
||||
server:
|
||||
address: ":3000"
|
||||
read_timeout: "10s"
|
||||
write_timeout: "10s"
|
||||
shutdown_timeout: "30s"
|
||||
prefork: false
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 6379
|
||||
password: ""
|
||||
db: 0
|
||||
pool_size: 10
|
||||
min_idle_conns: 5
|
||||
dial_timeout: "5s"
|
||||
read_timeout: "3s"
|
||||
write_timeout: "3s"
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
development: false
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: true
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 500
|
||||
max_backups: 90
|
||||
max_age: 90
|
||||
compress: true
|
||||
|
||||
middleware:
|
||||
enable_auth: true
|
||||
enable_rate_limiter: false
|
||||
rate_limiter:
|
||||
max: 100
|
||||
expiration: "1m"
|
||||
storage: "memory"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(initialContent), 0644); err != nil {
|
||||
t.Fatalf("failed to create config file: %v", err)
|
||||
func clearEnvVars(t *testing.T) {
|
||||
t.Helper()
|
||||
envVars := []string{
|
||||
"JUNHONG_DATABASE_HOST",
|
||||
"JUNHONG_DATABASE_PORT",
|
||||
"JUNHONG_DATABASE_USER",
|
||||
"JUNHONG_DATABASE_PASSWORD",
|
||||
"JUNHONG_DATABASE_DBNAME",
|
||||
"JUNHONG_REDIS_ADDRESS",
|
||||
"JUNHONG_REDIS_PORT",
|
||||
"JUNHONG_REDIS_PASSWORD",
|
||||
"JUNHONG_JWT_SECRET_KEY",
|
||||
"JUNHONG_SERVER_ADDRESS",
|
||||
"JUNHONG_LOGGING_LEVEL",
|
||||
}
|
||||
|
||||
// Set config path
|
||||
_ = os.Setenv(constants.EnvConfigPath, configFile)
|
||||
defer func() { _ = os.Unsetenv(constants.EnvConfigPath) }()
|
||||
|
||||
// Load initial config
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load initial config: %v", err)
|
||||
}
|
||||
|
||||
// Verify initial values
|
||||
if cfg.Logging.Level != "info" {
|
||||
t.Errorf("expected initial logging.level info, got %s", cfg.Logging.Level)
|
||||
}
|
||||
if cfg.Server.Address != ":3000" {
|
||||
t.Errorf("expected initial server.address :3000, got %s", cfg.Server.Address)
|
||||
}
|
||||
|
||||
// Modify config file
|
||||
updatedContent := `
|
||||
server:
|
||||
address: ":8080"
|
||||
read_timeout: "15s"
|
||||
write_timeout: "15s"
|
||||
shutdown_timeout: "30s"
|
||||
prefork: false
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 6379
|
||||
password: ""
|
||||
db: 0
|
||||
pool_size: 20
|
||||
min_idle_conns: 10
|
||||
dial_timeout: "5s"
|
||||
read_timeout: "3s"
|
||||
write_timeout: "3s"
|
||||
|
||||
logging:
|
||||
level: "debug"
|
||||
development: true
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: true
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 500
|
||||
max_backups: 90
|
||||
max_age: 90
|
||||
compress: true
|
||||
|
||||
middleware:
|
||||
enable_auth: false
|
||||
enable_rate_limiter: true
|
||||
rate_limiter:
|
||||
max: 200
|
||||
expiration: "2m"
|
||||
storage: "redis"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(updatedContent), 0644); err != nil {
|
||||
t.Fatalf("failed to update config file: %v", err)
|
||||
}
|
||||
|
||||
// Reload config
|
||||
newCfg, err := Reload()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to reload config: %v", err)
|
||||
}
|
||||
|
||||
// Verify updated values
|
||||
if newCfg.Logging.Level != "debug" {
|
||||
t.Errorf("expected updated logging.level debug, got %s", newCfg.Logging.Level)
|
||||
}
|
||||
if newCfg.Server.Address != ":8080" {
|
||||
t.Errorf("expected updated server.address :8080, got %s", newCfg.Server.Address)
|
||||
}
|
||||
if newCfg.Redis.PoolSize != 20 {
|
||||
t.Errorf("expected updated redis.pool_size 20, got %d", newCfg.Redis.PoolSize)
|
||||
}
|
||||
if newCfg.Middleware.EnableRateLimiter != true {
|
||||
t.Errorf("expected updated enable_rate_limiter true, got %v", newCfg.Middleware.EnableRateLimiter)
|
||||
}
|
||||
|
||||
// Verify global config was updated
|
||||
globalCfg := Get()
|
||||
if globalCfg.Logging.Level != "debug" {
|
||||
t.Errorf("expected global config updated, got logging.level %s", globalCfg.Logging.Level)
|
||||
for _, v := range envVars {
|
||||
os.Unsetenv(v)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetConfigPath tests the GetConfigPath function
|
||||
func TestGetConfigPath(t *testing.T) {
|
||||
// Reset viper
|
||||
viper.Reset()
|
||||
|
||||
// Create temp config file
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||
|
||||
content := `
|
||||
server:
|
||||
address: ":3000"
|
||||
read_timeout: "10s"
|
||||
write_timeout: "10s"
|
||||
shutdown_timeout: "30s"
|
||||
|
||||
redis:
|
||||
address: "localhost"
|
||||
port: 6379
|
||||
db: 0
|
||||
pool_size: 10
|
||||
min_idle_conns: 5
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
app_log:
|
||||
filename: "logs/app.log"
|
||||
max_size: 100
|
||||
max_backups: 30
|
||||
max_age: 30
|
||||
compress: true
|
||||
access_log:
|
||||
filename: "logs/access.log"
|
||||
max_size: 500
|
||||
max_backups: 90
|
||||
max_age: 90
|
||||
compress: true
|
||||
|
||||
middleware:
|
||||
enable_auth: true
|
||||
rate_limiter:
|
||||
max: 100
|
||||
expiration: "1m"
|
||||
storage: "memory"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("failed to create config file: %v", err)
|
||||
}
|
||||
|
||||
_ = os.Setenv(constants.EnvConfigPath, configFile)
|
||||
defer func() { _ = os.Unsetenv(constants.EnvConfigPath) }()
|
||||
|
||||
// Load config
|
||||
_, err := Load()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Get config path
|
||||
path := GetConfigPath()
|
||||
if path == "" {
|
||||
t.Error("expected non-empty config path")
|
||||
}
|
||||
|
||||
// Verify it's an absolute path
|
||||
if !filepath.IsAbs(path) {
|
||||
t.Errorf("expected absolute path, got %s", path)
|
||||
}
|
||||
func containsString(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > 0 && (s[:len(substr)] == substr || containsString(s[1:], substr)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user