8d631f8fab
Optimized README for better discoverability with keywords: api, habits, self-hosted. Consolidated content to reduce redundancy while maintaining clarity. Added comprehensive test coverage across multiple layers: - Infrastructure: bcrypt hashing, JWT tokens, configuration validation - Application commands: user deletion, password reset, token revocation, email verification - Application queries: login, token refresh - Domain entities: refresh tokens, password reset tokens Coverage increased from 42.5% to 50.8% with meaningful business logic tests. Fixed integration test handler initialization with correct parameters.
316 lines
8.2 KiB
Go
316 lines
8.2 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoad_Success(t *testing.T) {
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
defer clearEnv()
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
|
|
if cfg.DBPath != "/test/db.sqlite" {
|
|
t.Errorf("DBPath = %v, want %v", cfg.DBPath, "/test/db.sqlite")
|
|
}
|
|
if cfg.AppURL != "http://test.com" {
|
|
t.Errorf("AppURL = %v, want %v", cfg.AppURL, "http://test.com")
|
|
}
|
|
if cfg.JWTSecret != "test-secret" {
|
|
t.Errorf("JWTSecret = %v, want %v", cfg.JWTSecret, "test-secret")
|
|
}
|
|
if cfg.JWTExpiry != "1h" {
|
|
t.Errorf("JWTExpiry = %v, want %v", cfg.JWTExpiry, "1h")
|
|
}
|
|
if cfg.RefreshTokenExpiry != "7d" {
|
|
t.Errorf("RefreshTokenExpiry = %v, want %v", cfg.RefreshTokenExpiry, "7d")
|
|
}
|
|
if cfg.DefaultTimezone != "UTC" {
|
|
t.Errorf("DefaultTimezone = %v, want %v", cfg.DefaultTimezone, "UTC")
|
|
}
|
|
}
|
|
|
|
func TestLoad_WithDefaults(t *testing.T) {
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
defer clearEnv()
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
|
|
if cfg.Port != "8080" {
|
|
t.Errorf("Port = %v, want default %v", cfg.Port, "8080")
|
|
}
|
|
|
|
if cfg.SMTPPort != "587" {
|
|
t.Errorf("SMTPPort = %v, want default %v", cfg.SMTPPort, "587")
|
|
}
|
|
|
|
if cfg.SupportEmail != "contact@apocapoc.app" {
|
|
t.Errorf("SupportEmail = %v, want default %v", cfg.SupportEmail, "contact@apocapoc.app")
|
|
}
|
|
|
|
if cfg.SendWelcomeEmail != "false" {
|
|
t.Errorf("SendWelcomeEmail = %v, want default %v", cfg.SendWelcomeEmail, "false")
|
|
}
|
|
|
|
if cfg.RegistrationMode != "open" {
|
|
t.Errorf("RegistrationMode = %v, want default %v", cfg.RegistrationMode, "open")
|
|
}
|
|
}
|
|
|
|
func TestLoad_WithCustomDefaults(t *testing.T) {
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
os.Setenv("PORT", "3000")
|
|
os.Setenv("SMTP_PORT", "465")
|
|
os.Setenv("SUPPORT_EMAIL", "support@test.com")
|
|
os.Setenv("SEND_WELCOME_EMAIL", "true")
|
|
os.Setenv("REGISTRATION_MODE", "closed")
|
|
defer clearEnv()
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
|
|
if cfg.Port != "3000" {
|
|
t.Errorf("Port = %v, want %v", cfg.Port, "3000")
|
|
}
|
|
|
|
if cfg.SMTPPort != "465" {
|
|
t.Errorf("SMTPPort = %v, want %v", cfg.SMTPPort, "465")
|
|
}
|
|
|
|
if cfg.SupportEmail != "support@test.com" {
|
|
t.Errorf("SupportEmail = %v, want %v", cfg.SupportEmail, "support@test.com")
|
|
}
|
|
|
|
if cfg.SendWelcomeEmail != "true" {
|
|
t.Errorf("SendWelcomeEmail = %v, want %v", cfg.SendWelcomeEmail, "true")
|
|
}
|
|
|
|
if cfg.RegistrationMode != "closed" {
|
|
t.Errorf("RegistrationMode = %v, want %v", cfg.RegistrationMode, "closed")
|
|
}
|
|
}
|
|
|
|
func TestLoad_MissingDBPath(t *testing.T) {
|
|
clearEnv()
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
defer clearEnv()
|
|
|
|
_, err := Load()
|
|
if err == nil {
|
|
t.Fatal("Load() expected error for missing DB_PATH but got nil")
|
|
}
|
|
|
|
expectedMsg := "DB_PATH is required"
|
|
if err.Error() != expectedMsg {
|
|
t.Errorf("Load() error = %v, want %v", err.Error(), expectedMsg)
|
|
}
|
|
}
|
|
|
|
func TestLoad_MissingJWTSecret(t *testing.T) {
|
|
clearEnv()
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
defer clearEnv()
|
|
|
|
_, err := Load()
|
|
if err == nil {
|
|
t.Fatal("Load() expected error for missing JWT_SECRET but got nil")
|
|
}
|
|
|
|
expectedMsg := "JWT_SECRET is required"
|
|
if err.Error() != expectedMsg {
|
|
t.Errorf("Load() error = %v, want %v", err.Error(), expectedMsg)
|
|
}
|
|
}
|
|
|
|
func TestLoad_MissingJWTExpiry(t *testing.T) {
|
|
clearEnv()
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
defer clearEnv()
|
|
|
|
_, err := Load()
|
|
if err == nil {
|
|
t.Fatal("Load() expected error for missing JWT_EXPIRY but got nil")
|
|
}
|
|
|
|
expectedMsg := "JWT_EXPIRY is required"
|
|
if err.Error() != expectedMsg {
|
|
t.Errorf("Load() error = %v, want %v", err.Error(), expectedMsg)
|
|
}
|
|
}
|
|
|
|
func TestLoad_MissingRefreshTokenExpiry(t *testing.T) {
|
|
clearEnv()
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
defer clearEnv()
|
|
|
|
_, err := Load()
|
|
if err == nil {
|
|
t.Fatal("Load() expected error for missing REFRESH_TOKEN_EXPIRY but got nil")
|
|
}
|
|
|
|
expectedMsg := "REFRESH_TOKEN_EXPIRY is required"
|
|
if err.Error() != expectedMsg {
|
|
t.Errorf("Load() error = %v, want %v", err.Error(), expectedMsg)
|
|
}
|
|
}
|
|
|
|
func TestLoad_MissingDefaultTimezone(t *testing.T) {
|
|
clearEnv()
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
defer clearEnv()
|
|
|
|
_, err := Load()
|
|
if err == nil {
|
|
t.Fatal("Load() expected error for missing DEFAULT_TIMEZONE but got nil")
|
|
}
|
|
|
|
expectedMsg := "DEFAULT_TIMEZONE is required"
|
|
if err.Error() != expectedMsg {
|
|
t.Errorf("Load() error = %v, want %v", err.Error(), expectedMsg)
|
|
}
|
|
}
|
|
|
|
func TestLoad_WithSMTPConfig(t *testing.T) {
|
|
os.Setenv("DB_PATH", "/test/db.sqlite")
|
|
os.Setenv("APP_URL", "http://test.com")
|
|
os.Setenv("JWT_SECRET", "test-secret")
|
|
os.Setenv("JWT_EXPIRY", "1h")
|
|
os.Setenv("REFRESH_TOKEN_EXPIRY", "7d")
|
|
os.Setenv("DEFAULT_TIMEZONE", "UTC")
|
|
os.Setenv("SMTP_HOST", "smtp.test.com")
|
|
os.Setenv("SMTP_PORT", "587")
|
|
os.Setenv("SMTP_USER", "user@test.com")
|
|
os.Setenv("SMTP_PASSWORD", "test-password")
|
|
os.Setenv("SMTP_FROM", "noreply@test.com")
|
|
defer clearEnv()
|
|
|
|
cfg, err := Load()
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
|
|
if cfg.SMTPHost != "smtp.test.com" {
|
|
t.Errorf("SMTPHost = %v, want %v", cfg.SMTPHost, "smtp.test.com")
|
|
}
|
|
if cfg.SMTPPort != "587" {
|
|
t.Errorf("SMTPPort = %v, want %v", cfg.SMTPPort, "587")
|
|
}
|
|
if cfg.SMTPUser != "user@test.com" {
|
|
t.Errorf("SMTPUser = %v, want %v", cfg.SMTPUser, "user@test.com")
|
|
}
|
|
if cfg.SMTPPassword != "test-password" {
|
|
t.Errorf("SMTPPassword = %v, want %v", cfg.SMTPPassword, "test-password")
|
|
}
|
|
if cfg.SMTPFrom != "noreply@test.com" {
|
|
t.Errorf("SMTPFrom = %v, want %v", cfg.SMTPFrom, "noreply@test.com")
|
|
}
|
|
}
|
|
|
|
func TestGetEnvOrDefault(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
defaultValue string
|
|
envValue string
|
|
want string
|
|
}{
|
|
{
|
|
name: "env value exists",
|
|
key: "TEST_KEY",
|
|
defaultValue: "default",
|
|
envValue: "custom",
|
|
want: "custom",
|
|
},
|
|
{
|
|
name: "env value empty uses default",
|
|
key: "TEST_KEY_EMPTY",
|
|
defaultValue: "default",
|
|
envValue: "",
|
|
want: "default",
|
|
},
|
|
{
|
|
name: "env value not set uses default",
|
|
key: "TEST_KEY_NOT_SET",
|
|
defaultValue: "default",
|
|
envValue: "",
|
|
want: "default",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.envValue != "" {
|
|
os.Setenv(tt.key, tt.envValue)
|
|
defer os.Unsetenv(tt.key)
|
|
}
|
|
|
|
got := getEnvOrDefault(tt.key, tt.defaultValue)
|
|
if got != tt.want {
|
|
t.Errorf("getEnvOrDefault() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func clearEnv() {
|
|
os.Unsetenv("DB_PATH")
|
|
os.Unsetenv("PORT")
|
|
os.Unsetenv("APP_URL")
|
|
os.Unsetenv("JWT_SECRET")
|
|
os.Unsetenv("JWT_EXPIRY")
|
|
os.Unsetenv("REFRESH_TOKEN_EXPIRY")
|
|
os.Unsetenv("DEFAULT_TIMEZONE")
|
|
os.Unsetenv("SMTP_HOST")
|
|
os.Unsetenv("SMTP_PORT")
|
|
os.Unsetenv("SMTP_USER")
|
|
os.Unsetenv("SMTP_PASSWORD")
|
|
os.Unsetenv("SMTP_FROM")
|
|
os.Unsetenv("SUPPORT_EMAIL")
|
|
os.Unsetenv("SEND_WELCOME_EMAIL")
|
|
os.Unsetenv("REGISTRATION_MODE")
|
|
}
|