245 lines
7.9 KiB
Go
245 lines
7.9 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/infrastructure/config"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const testJWTSecret = "test-secret-0123456789abcdef0123456789"
|
|
|
|
func setBaseSettingsEnv(t *testing.T) {
|
|
t.Helper()
|
|
t.Setenv("ENVIRONMENT", "production")
|
|
t.Setenv("PUBLIC_URL", "https://cms.example.com")
|
|
t.Setenv("UPLOAD_DIR", "./data/uploads")
|
|
t.Setenv("JWT_SECRET", testJWTSecret)
|
|
t.Setenv("DB_FILE", "./data/renewcms.db")
|
|
t.Setenv("PORT", "8080")
|
|
t.Setenv("SMTP_LOGIN", "smtp-login@example.com")
|
|
t.Setenv("SMTP_SENDER", "notify@example.com")
|
|
t.Setenv("SMTP_PASSWORD", "hunter2")
|
|
t.Setenv("SMTP_HOST", "smtp.example.com")
|
|
t.Setenv("SMTP_PORT", "465")
|
|
t.Setenv("CORS_ALLOWED_ORIGINS", "")
|
|
t.Setenv("AUTH_RATE_LIMIT_MAX_ATTEMPTS", "5")
|
|
t.Setenv("AUTH_RATE_LIMIT_WINDOW_SECONDS", "60")
|
|
}
|
|
|
|
func TestNewSettingsFromEnvReadsAllFields(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("CORS_ALLOWED_ORIGINS", "https://cms.example.com;https://admin.example.com")
|
|
|
|
settings, err := config.NewSettingsFromEnv()
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
|
|
if settings.Environment != "production" {
|
|
t.Errorf("expected Environment %q, got %q", "production", settings.Environment)
|
|
}
|
|
if settings.PublicURL != "https://cms.example.com" {
|
|
t.Errorf("expected PublicURL %q, got %q", "https://cms.example.com", settings.PublicURL)
|
|
}
|
|
if settings.UploadDir != "./data/uploads" {
|
|
t.Errorf("expected UploadDir %q, got %q", "./data/uploads", settings.UploadDir)
|
|
}
|
|
if settings.JWTSecret != testJWTSecret {
|
|
t.Errorf("expected JWTSecret %q, got %q", testJWTSecret, settings.JWTSecret)
|
|
}
|
|
if settings.DBFile != "./data/renewcms.db" {
|
|
t.Errorf("expected DBFile %q, got %q", "./data/renewcms.db", settings.DBFile)
|
|
}
|
|
if settings.Port != "8080" {
|
|
t.Errorf("expected Port %q, got %q", "8080", settings.Port)
|
|
}
|
|
if settings.Smtp.Login != "smtp-login@example.com" {
|
|
t.Errorf("expected Smtp.Login %q, got %q", "smtp-login@example.com", settings.Smtp.Login)
|
|
}
|
|
if settings.Smtp.Sender != "notify@example.com" {
|
|
t.Errorf("expected Smtp.Sender %q, got %q", "notify@example.com", settings.Smtp.Sender)
|
|
}
|
|
if settings.Smtp.Password != "hunter2" {
|
|
t.Errorf("expected Smtp.Password %q, got %q", "hunter2", settings.Smtp.Password)
|
|
}
|
|
if settings.Smtp.Host != "smtp.example.com" {
|
|
t.Errorf("expected Smtp.Host %q, got %q", "smtp.example.com", settings.Smtp.Host)
|
|
}
|
|
if settings.Smtp.Port != 465 {
|
|
t.Errorf("expected Smtp.Port %d, got %d", 465, settings.Smtp.Port)
|
|
}
|
|
if settings.AuthRateLimit.MaxAttempts != 5 {
|
|
t.Errorf("expected AuthRateLimit.MaxAttempts %d, got %d", 5, settings.AuthRateLimit.MaxAttempts)
|
|
}
|
|
if settings.AuthRateLimit.Window != 60*time.Second {
|
|
t.Errorf("expected AuthRateLimit.Window %v, got %v", 60*time.Second, settings.AuthRateLimit.Window)
|
|
}
|
|
expectedOrigins := []string{"https://cms.example.com", "https://admin.example.com"}
|
|
if len(settings.CorsAllowedOrigins) != len(expectedOrigins) {
|
|
t.Fatalf("expected %d origins, got %v", len(expectedOrigins), settings.CorsAllowedOrigins)
|
|
}
|
|
for i, origin := range expectedOrigins {
|
|
if settings.CorsAllowedOrigins[i] != origin {
|
|
t.Errorf("expected origin %d to be %q, got %q", i, origin, settings.CorsAllowedOrigins[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvEmptyCorsOriginsIsNil(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
|
|
settings, err := config.NewSettingsFromEnv()
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
if settings.CorsAllowedOrigins != nil {
|
|
t.Errorf("expected nil CorsAllowedOrigins, got %v", settings.CorsAllowedOrigins)
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvSingleCorsOrigin(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("CORS_ALLOWED_ORIGINS", "https://cms.example.com")
|
|
|
|
settings, err := config.NewSettingsFromEnv()
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
if len(settings.CorsAllowedOrigins) != 1 || settings.CorsAllowedOrigins[0] != "https://cms.example.com" {
|
|
t.Errorf("expected a single origin %q, got %v", "https://cms.example.com", settings.CorsAllowedOrigins)
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvInvalidSmtpPortReturnsError(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("SMTP_PORT", "not-a-number")
|
|
|
|
_, err := config.NewSettingsFromEnv()
|
|
if err == nil {
|
|
t.Fatal("expected an error for a non-numeric SMTP_PORT")
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvInvalidAuthRateLimitMaxAttemptsReturnsError(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("AUTH_RATE_LIMIT_MAX_ATTEMPTS", "not-a-number")
|
|
|
|
_, err := config.NewSettingsFromEnv()
|
|
if err == nil {
|
|
t.Fatal("expected an error for a non-numeric AUTH_RATE_LIMIT_MAX_ATTEMPTS")
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvInvalidAuthRateLimitWindowSecondsReturnsError(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("AUTH_RATE_LIMIT_WINDOW_SECONDS", "not-a-number")
|
|
|
|
_, err := config.NewSettingsFromEnv()
|
|
if err == nil {
|
|
t.Fatal("expected an error for a non-numeric AUTH_RATE_LIMIT_WINDOW_SECONDS")
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvEmptyTrustedProxiesIsNil(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
|
|
settings, err := config.NewSettingsFromEnv()
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
if settings.TrustedProxies != nil {
|
|
t.Errorf("expected nil TrustedProxies, got %v", settings.TrustedProxies)
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvParsesMultipleTrustedProxies(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("TRUSTED_PROXIES", "10.0.0.1;127.0.0.1")
|
|
|
|
settings, err := config.NewSettingsFromEnv()
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
expected := []string{"10.0.0.1", "127.0.0.1"}
|
|
if len(settings.TrustedProxies) != len(expected) {
|
|
t.Fatalf("expected %d trusted proxies, got %v", len(expected), settings.TrustedProxies)
|
|
}
|
|
for i, proxy := range expected {
|
|
if settings.TrustedProxies[i] != proxy {
|
|
t.Errorf("expected trusted proxy %d to be %q, got %q", i, proxy, settings.TrustedProxies[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSettingsIsProductionFalseForNonProductionEnvironment(t *testing.T) {
|
|
settings := config.Settings{Environment: "development"}
|
|
if settings.IsProduction() {
|
|
t.Error("expected IsProduction to be false for environment \"development\"")
|
|
}
|
|
}
|
|
|
|
func TestNewSettingsFromEnvRefusesAWeakJWTSecret(t *testing.T) {
|
|
cases := map[string]string{
|
|
"empty": "",
|
|
"short": "test-secret",
|
|
"one byte short": "0123456789abcdef0123456789abcde",
|
|
"the .env.example placeholder": "change-me-to-a-long-random-string",
|
|
"the docker.env placeholder": "replace-with-output-of-openssl-rand-hex-32",
|
|
}
|
|
for name, secret := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("JWT_SECRET", secret)
|
|
|
|
_, err := config.NewSettingsFromEnv()
|
|
|
|
if !errors.Is(err, config.ErrWeakJWTSecret) {
|
|
t.Errorf("expected ErrWeakJWTSecret, got %v", err)
|
|
}
|
|
if err != nil && !strings.Contains(err.Error(), "openssl rand -hex 32") {
|
|
t.Errorf("expected the error to say how to generate a secret, got %q", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("A secret of exactly the minimum length is accepted", func(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("JWT_SECRET", strings.Repeat("k", config.MinJWTSecretBytes))
|
|
|
|
if _, err := config.NewSettingsFromEnv(); err != nil {
|
|
t.Errorf("expected no error, got %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNewSettingsFromEnvReadsSmtpAllowInsecure(t *testing.T) {
|
|
for raw, want := range map[string]bool{"": false, "false": false, "true": true, "TRUE": true} {
|
|
t.Run("value "+raw, func(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("SMTP_ALLOW_INSECURE", raw)
|
|
|
|
settings, err := config.NewSettingsFromEnv()
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if settings.Smtp.AllowInsecure != want {
|
|
t.Errorf("expected AllowInsecure %v, got %v", want, settings.Smtp.AllowInsecure)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("An unparsable value names the variable", func(t *testing.T) {
|
|
setBaseSettingsEnv(t)
|
|
t.Setenv("SMTP_ALLOW_INSECURE", "maybe")
|
|
|
|
_, err := config.NewSettingsFromEnv()
|
|
|
|
if err == nil || !strings.Contains(err.Error(), "SMTP_ALLOW_INSECURE") {
|
|
t.Errorf("expected an error naming SMTP_ALLOW_INSECURE, got %v", err)
|
|
}
|
|
})
|
|
}
|