package config import ( "bytes" "os" "path/filepath" "strings" "testing" "github.com/spf13/cobra" ) func TestEnvOverridesYAML(t *testing.T) { tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.yaml") content := ` pg: host: localhost port: 5433 backup: retention_days: 90 ` if err := os.WriteFile(configPath, []byte(content), 0o644); err != nil { t.Fatalf("failed to write test config: %v", err) } t.Setenv("APP_CONFIG_LOCATION", configPath) t.Setenv("APP_PG_HOST", "remote") cmd := &cobra.Command{} RegisterFlags(cmd) var buf bytes.Buffer oldOutput := outputWriter outputWriter = &buf defer func() { outputWriter = oldOutput }() cfg, err := Load(cmd) if err != nil { t.Fatalf("Load failed: %v", err) } if cfg.PG.Host != "remote" { t.Errorf("PG.Host = %q, want %q (env should override YAML)", cfg.PG.Host, "remote") } if cfg.PG.Port != 5433 { t.Errorf("PG.Port = %d, want %d (YAML value should be preserved when env not set)", cfg.PG.Port, 5433) } if cfg.Backup.RetentionDays != 90 { t.Errorf("Backup.RetentionDays = %d, want %d (from YAML)", cfg.Backup.RetentionDays, 90) } logOutput := buf.String() if !strings.Contains(logOutput, configPath) { t.Errorf("log output did not contain config path %q: %s", configPath, logOutput) } } func TestDefaults(t *testing.T) { cmd := &cobra.Command{} RegisterFlags(cmd) var buf bytes.Buffer oldOutput := outputWriter outputWriter = &buf defer func() { outputWriter = oldOutput }() cfg, err := Load(cmd) if err != nil { t.Fatalf("Load failed: %v", err) } if cfg.PG.Port != 5432 { t.Errorf("PG.Port = %d, want %d", cfg.PG.Port, 5432) } if cfg.PG.SSLMode != "prefer" { t.Errorf("PG.SSLMode = %q, want %q", cfg.PG.SSLMode, "prefer") } if len(cfg.PG.ExcludeTables) != 1 || cfg.PG.ExcludeTables[0] != "e2e_one_time_keys_json" { t.Errorf("PG.ExcludeTables = %v, want [e2e_one_time_keys_json]", cfg.PG.ExcludeTables) } if cfg.Backup.RetentionDays != 180 { t.Errorf("Backup.RetentionDays = %d, want %d", cfg.Backup.RetentionDays, 180) } if cfg.Backup.Cron != "0 0 3 * * *" { t.Errorf("Backup.Cron = %q, want %q", cfg.Backup.Cron, "0 0 3 * * *") } if cfg.PQScheme != 0x0006 { t.Errorf("PQScheme = 0x%04x, want 0x%04x", cfg.PQScheme, 0x0006) } if cfg.ClassicalScheme != 0x0007 { t.Errorf("ClassicalScheme = 0x%04x, want 0x%04x", cfg.ClassicalScheme, 0x0007) } if cfg.Healthz.Port != 8080 { t.Errorf("Healthz.Port = %d, want %d", cfg.Healthz.Port, 8080) } if cfg.Log.Level != "info" { t.Errorf("Log.Level = %q, want %q", cfg.Log.Level, "info") } logOutput := buf.String() if !strings.Contains(logOutput, "Configuration file not found") { t.Errorf("log output did not contain 'Configuration file not found': %s", logOutput) } } func TestFlagsOverrideEnv(t *testing.T) { t.Setenv("APP_PG_HOST", "env-host") cmd := &cobra.Command{} RegisterFlags(cmd) if err := cmd.ParseFlags([]string{"--pg-host", "flag-host"}); err != nil { t.Fatalf("ParseFlags failed: %v", err) } var buf bytes.Buffer oldOutput := outputWriter outputWriter = &buf defer func() { outputWriter = oldOutput }() cfg, err := Load(cmd) if err != nil { t.Fatalf("Load failed: %v", err) } if cfg.PG.Host != "flag-host" { t.Errorf("PG.Host = %q, want %q (flag should override env)", cfg.PG.Host, "flag-host") } } func TestAllConfigFields(t *testing.T) { tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.yaml") content := ` pg: host: db.example.com port: 5432 user: synapse password: secret database: synapse_db sslmode: require exclude_tables: - table1 - table2 backup: dir: /backups retention_days: 30 cron: "0 0 * * *" pq_scheme: 0x0001 classical_scheme: 0x0002 pq_public_key_path: /keys/pq.pub classical_public_key_path: /keys/classical.pub healthz: port: 9090 log: level: debug ` if err := os.WriteFile(configPath, []byte(content), 0o644); err != nil { t.Fatalf("failed to write test config: %v", err) } t.Setenv("APP_CONFIG_LOCATION", configPath) cmd := &cobra.Command{} RegisterFlags(cmd) var buf bytes.Buffer oldOutput := outputWriter outputWriter = &buf defer func() { outputWriter = oldOutput }() cfg, err := Load(cmd) if err != nil { t.Fatalf("Load failed: %v", err) } if cfg.PG.Host != "db.example.com" { t.Errorf("PG.Host = %q, want %q", cfg.PG.Host, "db.example.com") } if cfg.PG.User != "synapse" { t.Errorf("PG.User = %q, want %q", cfg.PG.User, "synapse") } if cfg.PG.Password != "secret" { t.Errorf("PG.Password = %q, want %q", cfg.PG.Password, "secret") } if cfg.PG.Database != "synapse_db" { t.Errorf("PG.Database = %q, want %q", cfg.PG.Database, "synapse_db") } if cfg.PG.SSLMode != "require" { t.Errorf("PG.SSLMode = %q, want %q", cfg.PG.SSLMode, "require") } if len(cfg.PG.ExcludeTables) != 2 || cfg.PG.ExcludeTables[0] != "table1" { t.Errorf("PG.ExcludeTables = %v, want [table1 table2]", cfg.PG.ExcludeTables) } if cfg.Backup.Dir != "/backups" { t.Errorf("Backup.Dir = %q, want %q", cfg.Backup.Dir, "/backups") } if cfg.Backup.Cron != "0 0 * * *" { t.Errorf("Backup.Cron = %q, want %q", cfg.Backup.Cron, "0 0 * * *") } if cfg.PQScheme != 0x0001 { t.Errorf("PQScheme = 0x%04x, want 0x%04x", cfg.PQScheme, 0x0001) } if cfg.ClassicalScheme != 0x0002 { t.Errorf("ClassicalScheme = 0x%04x, want 0x%04x", cfg.ClassicalScheme, 0x0002) } if cfg.PQPublicKeyPath != "/keys/pq.pub" { t.Errorf("PQPublicKeyPath = %q, want %q", cfg.PQPublicKeyPath, "/keys/pq.pub") } if cfg.ClassicalPublicKeyPath != "/keys/classical.pub" { t.Errorf("ClassicalPublicKeyPath = %q, want %q", cfg.ClassicalPublicKeyPath, "/keys/classical.pub") } if cfg.Healthz.Port != 9090 { t.Errorf("Healthz.Port = %d, want %d", cfg.Healthz.Port, 9090) } if cfg.Log.Level != "debug" { t.Errorf("Log.Level = %q, want %q", cfg.Log.Level, "debug") } logOutput := buf.String() if !strings.Contains(logOutput, configPath) { t.Errorf("log output did not contain config path %q: %s", configPath, logOutput) } }