package config import ( "context" "os" "path/filepath" "sync" "testing" ) func TestGetEnv(t *testing.T) { // Set a test env var t.Setenv("TEST_CONFIG_VAR", "test_value") tests := []struct { name string key string defaultVal string want string }{ {"existing var", "TEST_CONFIG_VAR", "default", "test_value"}, {"non-existing var", "NON_EXISTING_VAR", "default", "default"}, {"empty default", "NON_EXISTING_VAR_2", "", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := getEnv(tt.key, tt.defaultVal) if got != tt.want { t.Errorf("getEnv(%s, %s) = %s, want %s", tt.key, tt.defaultVal, got, tt.want) } }) } } func TestConfig_WebhookURL_ThreadSafe(t *testing.T) { cfg := &Config{} cfg.webhookURL = "https://discord.com/api/webhooks/test" // Test concurrent reads and writes var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(2) go func() { defer wg.Done() _ = cfg.WebhookURL() }() go func() { defer wg.Done() cfg.mu.Lock() cfg.webhookURL = "updated" cfg.mu.Unlock() }() } wg.Wait() // Should not race } func TestConfig_LoadWebhookFromSecret(t *testing.T) { // Create temp directory with secret file tmpDir := t.TempDir() secretPath := filepath.Join(tmpDir, "webhook-url") webhookURL := "https://discord.com/api/webhooks/123/abc" if err := os.WriteFile(secretPath, []byte(webhookURL+"\n"), 0644); err != nil { t.Fatalf("failed to write secret file: %v", err) } cfg := &Config{ SecretsPath: tmpDir, } if err := cfg.loadWebhookFromSecret(); err != nil { t.Fatalf("loadWebhookFromSecret() error = %v", err) } if cfg.webhookURL != webhookURL { t.Errorf("webhookURL = %s, want %s", cfg.webhookURL, webhookURL) } } func TestConfig_LoadWebhookFromSecret_NotFound(t *testing.T) { cfg := &Config{ SecretsPath: "/nonexistent/path", } err := cfg.loadWebhookFromSecret() if err == nil { t.Error("expected error for non-existent secret") } } func TestConfig_LoadWebhookFromSecret_TrimsWhitespace(t *testing.T) { tmpDir := t.TempDir() secretPath := filepath.Join(tmpDir, "webhook-url") // Write with extra whitespace if err := os.WriteFile(secretPath, []byte(" https://example.com \n\n"), 0644); err != nil { t.Fatalf("failed to write secret file: %v", err) } cfg := &Config{SecretsPath: tmpDir} if err := cfg.loadWebhookFromSecret(); err != nil { t.Fatalf("loadWebhookFromSecret() error = %v", err) } if cfg.webhookURL != "https://example.com" { t.Errorf("webhookURL = %q, want %q", cfg.webhookURL, "https://example.com") } } func TestLoad_ParsesTopics(t *testing.T) { t.Setenv("NTFY_TOPICS", "alerts, updates , notifications") t.Setenv("VAULT_ENABLED", "false") cfg, err := Load(context.Background()) if err != nil { t.Fatalf("Load() error = %v", err) } expected := []string{"alerts", "updates", "notifications"} if len(cfg.NtfyTopics) != len(expected) { t.Errorf("NtfyTopics length = %d, want %d", len(cfg.NtfyTopics), len(expected)) } for i, topic := range cfg.NtfyTopics { if topic != expected[i] { t.Errorf("NtfyTopics[%d] = %s, want %s", i, topic, expected[i]) } } } func TestLoad_Defaults(t *testing.T) { // Clear any existing env vars t.Setenv("NTFY_URL", "") t.Setenv("HTTP_PORT", "") t.Setenv("VAULT_ENABLED", "") cfg, err := Load(context.Background()) if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.NtfyURL != "http://ntfy.observability.svc.cluster.local" { t.Errorf("NtfyURL = %s, want default", cfg.NtfyURL) } if cfg.HTTPPort != "8080" { t.Errorf("HTTPPort = %s, want 8080", cfg.HTTPPort) } if cfg.VaultEnabled { t.Error("VaultEnabled should be false by default") } } func TestLoad_VaultEnabled(t *testing.T) { t.Setenv("VAULT_ENABLED", "true") // This will fail to init Vault (no server), but should gracefully fall back cfg, err := Load(context.Background()) if err != nil { t.Fatalf("Load() error = %v", err) } if !cfg.VaultEnabled { t.Error("VaultEnabled should be true") } // vaultClient should be nil (failed to connect) if cfg.vaultClient != nil { t.Error("vaultClient should be nil when Vault unavailable") } } func TestLoad_FallsBackToEnvVar(t *testing.T) { webhookURL := "https://discord.com/api/webhooks/env/test" t.Setenv("DISCORD_WEBHOOK_URL", webhookURL) t.Setenv("VAULT_ENABLED", "false") cfg, err := Load(context.Background()) if err != nil { t.Fatalf("Load() error = %v", err) } if cfg.WebhookURL() != webhookURL { t.Errorf("WebhookURL() = %s, want %s", cfg.WebhookURL(), webhookURL) } } func TestConfig_Close_NoVault(t *testing.T) { cfg := &Config{} // Should not panic with nil vaultClient err := cfg.Close() if err != nil { t.Errorf("Close() error = %v", err) } } func TestConfig_WatchSecrets_NoPath(t *testing.T) { cfg := &Config{ SecretsPath: "", vaultClient: nil, } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Should return immediately, not block or panic cfg.WatchSecrets(ctx) }