package config import ( "fmt" "os" "path/filepath" "testing" ) func TestLoadAppliesDefaultsAndReadsSecrets(t *testing.T) { t.Parallel() tempDir := t.TempDir() keyPath := filepath.Join(tempDir, "api-key") secretPath := filepath.Join(tempDir, "api-secret") if err := os.WriteFile(keyPath, []byte("test-key\n"), 0o600); err != nil { t.Fatalf("write key file: %v", err) } if err := os.WriteFile(secretPath, []byte("test-secret\n"), 0o600); err != nil { t.Fatalf("write secret file: %v", err) } configPath := filepath.Join(tempDir, "config.yaml") payload := fmt.Sprintf(`storage: path: %s/data/blocker.db opnsense: enabled: true base_url: https://router.example.test api_key_file: %s api_secret_file: %s ensure_alias: true alias: name: blocked-ips profiles: main: auto_block: true block_unexpected_posts: true block_php_paths: true allowed_post_paths: - /search suspicious_path_prefixes: - /wp-admin excluded_cidrs: - 10.0.0.0/8 known_agents: - name: friendly-bot decision: allow user_agent_prefixes: - FriendlyBot/ sources: - name: main path: %s/access.json profile: main `, tempDir, keyPath, secretPath, tempDir) if err := os.WriteFile(configPath, []byte(payload), 0o600); err != nil { t.Fatalf("write config file: %v", err) } cfg, err := Load(configPath) if err != nil { t.Fatalf("load config: %v", err) } if got, want := cfg.Server.ListenAddress, "127.0.0.1:9080"; got != want { t.Fatalf("unexpected listen address: got %q want %q", got, want) } if got, want := cfg.Sources[0].InitialPosition, "end"; got != want { t.Fatalf("unexpected initial position: got %q want %q", got, want) } if got, want := cfg.OPNsense.APIKey, "test-key"; got != want { t.Fatalf("unexpected api key: got %q want %q", got, want) } if got, want := cfg.OPNsense.APISecret, "test-secret"; got != want { t.Fatalf("unexpected api secret: got %q want %q", got, want) } if got := cfg.Investigation.BackgroundLookback.Duration; got != 0 { t.Fatalf("unexpected background lookback default: got %s want 0", got) } if !cfg.Investigation.Enabled { t.Fatalf("expected investigation to default to enabled") } if !cfg.Investigation.SpamhausEnabled { t.Fatalf("expected spamhaus checks to default to enabled") } if !cfg.OPNsense.EnsureAlias { t.Fatalf("expected ensure_alias to default to enabled") } profile := cfg.Profiles["main"] if !profile.IsAllowedPostPath("/search") { t.Fatalf("expected /search to be normalized as an allowed POST path") } if len(profile.SuspiciousPrefixes()) != 1 || profile.SuspiciousPrefixes()[0] != "/wp-admin" { t.Fatalf("unexpected suspicious prefixes: %#v", profile.SuspiciousPrefixes()) } } func TestLoadRejectsInvalidInitialPosition(t *testing.T) { t.Parallel() tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.yaml") payload := fmt.Sprintf(`profiles: main: auto_block: true sources: - name: main path: %s/access.json profile: main initial_position: sideways `, tempDir) if err := os.WriteFile(configPath, []byte(payload), 0o600); err != nil { t.Fatalf("write config file: %v", err) } if _, err := Load(configPath); err == nil { t.Fatalf("expected invalid initial_position to be rejected") } } func TestLoadPreservesExplicitFalseForTrueDefaults(t *testing.T) { t.Parallel() tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.yaml") payload := fmt.Sprintf(`investigation: enabled: false spamhaus_enabled: false opnsense: enabled: false ensure_alias: false profiles: main: auto_block: true sources: - name: main path: %s/access.json profile: main `, tempDir) if err := os.WriteFile(configPath, []byte(payload), 0o600); err != nil { t.Fatalf("write config file: %v", err) } cfg, err := Load(configPath) if err != nil { t.Fatalf("load config: %v", err) } if cfg.Investigation.Enabled { t.Fatalf("expected investigation.enabled=false to be preserved") } if cfg.Investigation.SpamhausEnabled { t.Fatalf("expected investigation.spamhaus_enabled=false to be preserved") } if cfg.OPNsense.EnsureAlias { t.Fatalf("expected opnsense.ensure_alias=false to be preserved") } }