From 9a4807e576ccff5013f13ea8735d4c254e7b6485 Mon Sep 17 00:00:00 2001 From: Robert Nubel Date: Wed, 1 Apr 2026 10:44:37 -0500 Subject: [PATCH] fix: ignore missing default config file; error only when --config-file is explicit --- pgmgr/config.go | 8 ++++-- pgmgr/config_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/pgmgr/config.go b/pgmgr/config.go index 809810e..6a9b579 100644 --- a/pgmgr/config.go +++ b/pgmgr/config.go @@ -19,6 +19,7 @@ type argumentContext interface { Int(string) int StringSlice(string) []string Bool(string) bool + IsSet(string) bool } // Config stores the options used by pgmgr. @@ -55,7 +56,7 @@ func LoadConfig(config *Config, ctx argumentContext) error { // load configuration from file first; then override with // flags or env vars if they're present. configFile := ctx.String("config-file") - if err := config.populateFromFile(configFile); err != nil { + if err := config.populateFromFile(configFile, ctx.IsSet("config-file")); err != nil { return err } @@ -77,12 +78,15 @@ func LoadConfig(config *Config, ctx argumentContext) error { return config.validate() } -func (config *Config) populateFromFile(configFile string) error { +func (config *Config) populateFromFile(configFile string, explicit bool) error { if configFile == "" { return nil } contents, err := os.ReadFile(configFile) if err != nil { + if !explicit && errors.Is(err, os.ErrNotExist) { + return nil + } return err } if err := json.Unmarshal(contents, &config); err != nil { diff --git a/pgmgr/config_test.go b/pgmgr/config_test.go index 69ca64d..edb8c17 100644 --- a/pgmgr/config_test.go +++ b/pgmgr/config_test.go @@ -3,6 +3,7 @@ package pgmgr import ( "fmt" "os" + "path/filepath" "testing" ) @@ -12,6 +13,7 @@ type TestContext struct { IntVals map[string]int StringSliceVals map[string][]string BoolVals map[string]bool + IsSetVals map[string]bool } func (t *TestContext) String(key string) string { @@ -26,6 +28,9 @@ func (t *TestContext) StringSlice(key string) []string { func (t *TestContext) Bool(key string) bool { return t.BoolVals[key] } +func (t *TestContext) IsSet(key string) bool { + return t.IsSetVals[key] +} func TestDefaults(t *testing.T) { c := &Config{} @@ -193,3 +198,56 @@ func TestQuotedMigrationTable(t *testing.T) { t.Fatal(`Schema-qualified migration table should be "abc"."def", got`, c.quotedMigrationTable()) } } + +func TestMissingDefaultConfigFile(t *testing.T) { + // Simulate production: no .pgmgr.json in the working dir, + // config-file flag was NOT explicitly set by the user. + c := &Config{} + ctx := &TestContext{ + StringVals: map[string]string{ + "config-file": filepath.Join(t.TempDir(), ".pgmgr.json"), + }, + IsSetVals: map[string]bool{ + "config-file": false, // key distinction: user did not pass --config-file + }, + } + + if err := LoadConfig(c, ctx); err != nil { + t.Fatal("LoadConfig should succeed when default config file is missing, but got:", err) + } +} + +func TestExplicitMissingConfigFileErrors(t *testing.T) { + c := &Config{} + ctx := &TestContext{ + StringVals: map[string]string{ + "config-file": filepath.Join(t.TempDir(), "explicit-missing.json"), + }, + IsSetVals: map[string]bool{ + "config-file": true, // user explicitly passed --config-file + }, + } + + if err := LoadConfig(c, ctx); err == nil { + t.Fatal("LoadConfig should fail when explicitly-given config file is missing") + } +} + +func TestExplicitEnvVarMissingConfigFileErrors(t *testing.T) { + // When PGMGR_CONFIG_FILE env var is set to a path that doesn't exist, + // LoadConfig should fail — the user explicitly configured a path. + missingPath := filepath.Join(t.TempDir(), "from-env-var.json") + c := &Config{} + ctx := &TestContext{ + StringVals: map[string]string{ + "config-file": missingPath, + }, + IsSetVals: map[string]bool{ + "config-file": true, // urfave/cli sets IsSet=true when EnvVar is present + }, + } + + if err := LoadConfig(c, ctx); err == nil { + t.Fatal("LoadConfig should fail when config file path from env var does not exist") + } +}