From 1150c4ba39eeec705215498ec76bc30ccdec2eec Mon Sep 17 00:00:00 2001 From: LordMathis Date: Sat, 23 Nov 2024 16:36:29 +0100 Subject: [PATCH] Test config package --- server/internal/config/config.go | 18 +-- server/internal/config/config_test.go | 215 ++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 server/internal/config/config_test.go diff --git a/server/internal/config/config.go b/server/internal/config/config.go index 90049d3..0cfa5f7 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -1,9 +1,9 @@ +// Package config provides the configuration for the application package config import ( "fmt" "os" - "path/filepath" "strconv" "strings" "time" @@ -11,6 +11,7 @@ import ( "novamd/internal/crypto" ) +// Config holds the configuration for the application type Config struct { DBPath string WorkDir string @@ -27,6 +28,7 @@ type Config struct { IsDevelopment bool } +// DefaultConfig returns a new Config instance with default values func DefaultConfig() *Config { return &Config{ DBPath: "./novamd.db", @@ -39,6 +41,7 @@ func DefaultConfig() *Config { } } +// Validate checks if the configuration is valid func (c *Config) Validate() error { if c.AdminEmail == "" || c.AdminPassword == "" { return fmt.Errorf("NOVAMD_ADMIN_EMAIL and NOVAMD_ADMIN_PASSWORD must be set") @@ -63,16 +66,10 @@ func Load() (*Config, error) { if dbPath := os.Getenv("NOVAMD_DB_PATH"); dbPath != "" { config.DBPath = dbPath } - if err := ensureDir(filepath.Dir(config.DBPath)); err != nil { - return nil, fmt.Errorf("failed to create database directory: %w", err) - } if workDir := os.Getenv("NOVAMD_WORKDIR"); workDir != "" { config.WorkDir = workDir } - if err := ensureDir(config.WorkDir); err != nil { - return nil, fmt.Errorf("failed to create work directory: %w", err) - } if staticPath := os.Getenv("NOVAMD_STATIC_PATH"); staticPath != "" { config.StaticPath = staticPath @@ -115,10 +112,3 @@ func Load() (*Config, error) { return config, nil } - -func ensureDir(dir string) error { - if dir == "" { - return nil - } - return os.MkdirAll(dir, 0755) -} diff --git a/server/internal/config/config_test.go b/server/internal/config/config_test.go new file mode 100644 index 0000000..51aef69 --- /dev/null +++ b/server/internal/config/config_test.go @@ -0,0 +1,215 @@ +package config_test + +import ( + "os" + "testing" + "time" + + "novamd/internal/config" +) + +func TestDefaultConfig(t *testing.T) { + cfg := config.DefaultConfig() + + tests := []struct { + name string + got interface{} + expected interface{} + }{ + {"DBPath", cfg.DBPath, "./novamd.db"}, + {"WorkDir", cfg.WorkDir, "./data"}, + {"StaticPath", cfg.StaticPath, "../app/dist"}, + {"Port", cfg.Port, "8080"}, + {"RateLimitRequests", cfg.RateLimitRequests, 100}, + {"RateLimitWindow", cfg.RateLimitWindow, time.Minute * 15}, + {"IsDevelopment", cfg.IsDevelopment, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("DefaultConfig().%s = %v, want %v", tt.name, tt.got, tt.expected) + } + }) + } +} + +// setEnv is a helper function to set environment variables and check for errors +func setEnv(t *testing.T, key, value string) { + if err := os.Setenv(key, value); err != nil { + t.Fatalf("Failed to set environment variable %s: %v", key, err) + } +} + +func TestLoad(t *testing.T) { + // Helper function to reset environment variables + cleanup := func() { + envVars := []string{ + "NOVAMD_ENV", + "NOVAMD_DB_PATH", + "NOVAMD_WORKDIR", + "NOVAMD_STATIC_PATH", + "NOVAMD_PORT", + "NOVAMD_APP_URL", + "NOVAMD_CORS_ORIGINS", + "NOVAMD_ADMIN_EMAIL", + "NOVAMD_ADMIN_PASSWORD", + "NOVAMD_ENCRYPTION_KEY", + "NOVAMD_JWT_SIGNING_KEY", + "NOVAMD_RATE_LIMIT_REQUESTS", + "NOVAMD_RATE_LIMIT_WINDOW", + } + for _, env := range envVars { + if err := os.Unsetenv(env); err != nil { + t.Fatalf("Failed to unset environment variable %s: %v", env, err) + } + } + } + + t.Run("load with defaults", func(t *testing.T) { + cleanup() + defer cleanup() + + // Set required env vars + setEnv(t, "NOVAMD_ADMIN_EMAIL", "admin@example.com") + setEnv(t, "NOVAMD_ADMIN_PASSWORD", "password123") + setEnv(t, "NOVAMD_ENCRYPTION_KEY", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=") // 32 bytes base64 encoded + + cfg, err := config.Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + if cfg.DBPath != "./novamd.db" { + t.Errorf("default DBPath = %v, want %v", cfg.DBPath, "./novamd.db") + } + }) + + t.Run("load with custom values", func(t *testing.T) { + cleanup() + defer cleanup() + + // Set all environment variables + envs := map[string]string{ + "NOVAMD_ENV": "development", + "NOVAMD_DB_PATH": "/custom/db/path.db", + "NOVAMD_WORKDIR": "/custom/work/dir", + "NOVAMD_STATIC_PATH": "/custom/static/path", + "NOVAMD_PORT": "3000", + "NOVAMD_APP_URL": "http://localhost:3000", + "NOVAMD_CORS_ORIGINS": "http://localhost:3000,http://localhost:3001", + "NOVAMD_ADMIN_EMAIL": "admin@example.com", + "NOVAMD_ADMIN_PASSWORD": "password123", + "NOVAMD_ENCRYPTION_KEY": "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=", + "NOVAMD_JWT_SIGNING_KEY": "secret-key", + "NOVAMD_RATE_LIMIT_REQUESTS": "200", + "NOVAMD_RATE_LIMIT_WINDOW": "30m", + } + + for k, v := range envs { + setEnv(t, k, v) + } + + cfg, err := config.Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + tests := []struct { + name string + got interface{} + expected interface{} + }{ + {"IsDevelopment", cfg.IsDevelopment, true}, + {"DBPath", cfg.DBPath, "/custom/db/path.db"}, + {"WorkDir", cfg.WorkDir, "/custom/work/dir"}, + {"StaticPath", cfg.StaticPath, "/custom/static/path"}, + {"Port", cfg.Port, "3000"}, + {"AppURL", cfg.AppURL, "http://localhost:3000"}, + {"AdminEmail", cfg.AdminEmail, "admin@example.com"}, + {"AdminPassword", cfg.AdminPassword, "password123"}, + {"JWTSigningKey", cfg.JWTSigningKey, "secret-key"}, + {"RateLimitRequests", cfg.RateLimitRequests, 200}, + {"RateLimitWindow", cfg.RateLimitWindow, 30 * time.Minute}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.expected { + t.Errorf("%s = %v, want %v", tt.name, tt.got, tt.expected) + } + }) + } + + // Test CORS origins separately as it's a slice + expectedOrigins := []string{"http://localhost:3000", "http://localhost:3001"} + if len(cfg.CORSOrigins) != len(expectedOrigins) { + t.Errorf("CORSOrigins length = %v, want %v", len(cfg.CORSOrigins), len(expectedOrigins)) + } + for i, origin := range cfg.CORSOrigins { + if origin != expectedOrigins[i] { + t.Errorf("CORSOrigins[%d] = %v, want %v", i, origin, expectedOrigins[i]) + } + } + }) + + t.Run("validation failures", func(t *testing.T) { + testCases := []struct { + name string + setupEnv func(*testing.T) + expectedError string + }{ + { + name: "missing admin email", + setupEnv: func(t *testing.T) { + cleanup() + setEnv(t, "NOVAMD_ADMIN_PASSWORD", "password123") + setEnv(t, "NOVAMD_ENCRYPTION_KEY", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=") + }, + expectedError: "NOVAMD_ADMIN_EMAIL and NOVAMD_ADMIN_PASSWORD must be set", + }, + { + name: "missing admin password", + setupEnv: func(t *testing.T) { + cleanup() + setEnv(t, "NOVAMD_ADMIN_EMAIL", "admin@example.com") + setEnv(t, "NOVAMD_ENCRYPTION_KEY", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=") + }, + expectedError: "NOVAMD_ADMIN_EMAIL and NOVAMD_ADMIN_PASSWORD must be set", + }, + { + name: "missing encryption key", + setupEnv: func(t *testing.T) { + cleanup() + setEnv(t, "NOVAMD_ADMIN_EMAIL", "admin@example.com") + setEnv(t, "NOVAMD_ADMIN_PASSWORD", "password123") + }, + expectedError: "invalid NOVAMD_ENCRYPTION_KEY: encryption key is required", + }, + { + name: "invalid encryption key", + setupEnv: func(t *testing.T) { + cleanup() + setEnv(t, "NOVAMD_ADMIN_EMAIL", "admin@example.com") + setEnv(t, "NOVAMD_ADMIN_PASSWORD", "password123") + setEnv(t, "NOVAMD_ENCRYPTION_KEY", "invalid-key") + }, + expectedError: "invalid NOVAMD_ENCRYPTION_KEY: invalid base64 encoding: illegal base64 data at input byte 7", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tc.setupEnv(t) + _, err := config.Load() + if err == nil { + t.Error("expected error, got nil") + return + } + if err.Error() != tc.expectedError { + t.Errorf("error = %v, want error containing %v", err, tc.expectedError) + } + }) + } + }) +}