package postgres import ( "strings" "testing" "time" ) func TestDefaultConfigReturnsExpectedTuning(t *testing.T) { t.Parallel() cfg := DefaultConfig() if cfg.OperationTimeout != DefaultOperationTimeout { t.Fatalf("operation timeout = %v, want %v", cfg.OperationTimeout, DefaultOperationTimeout) } if cfg.MaxOpenConns != DefaultMaxOpenConns { t.Fatalf("max open conns = %d, want %d", cfg.MaxOpenConns, DefaultMaxOpenConns) } if cfg.MaxIdleConns != DefaultMaxIdleConns { t.Fatalf("max idle conns = %d, want %d", cfg.MaxIdleConns, DefaultMaxIdleConns) } if cfg.ConnMaxLifetime != DefaultConnMaxLifetime { t.Fatalf("conn max lifetime = %v, want %v", cfg.ConnMaxLifetime, DefaultConnMaxLifetime) } } func TestConfigValidateAcceptsHappyPath(t *testing.T) { t.Parallel() cfg := DefaultConfig() cfg.PrimaryDSN = "postgres://localhost:5432/galaxy?sslmode=disable" if err := cfg.Validate(); err != nil { t.Fatalf("validate happy path: %v", err) } } func TestConfigValidateRejectsInvalidValues(t *testing.T) { t.Parallel() tests := []struct { name string mutate func(*Config) wantSub string }{ { name: "missing primary", mutate: func(c *Config) { c.PrimaryDSN = "" }, wantSub: "primary DSN", }, { name: "blank replica entry", mutate: func(c *Config) { c.ReplicaDSNs = []string{"postgres://a", " "} }, wantSub: "replica DSN", }, { name: "non-positive timeout", mutate: func(c *Config) { c.OperationTimeout = 0 }, wantSub: "operation timeout", }, { name: "non-positive max open", mutate: func(c *Config) { c.MaxOpenConns = 0 }, wantSub: "max open conns", }, { name: "negative max idle", mutate: func(c *Config) { c.MaxIdleConns = -1 }, wantSub: "max idle conns must not be negative", }, { name: "max idle exceeds open", mutate: func(c *Config) { c.MaxOpenConns = 4 c.MaxIdleConns = 5 }, wantSub: "must not exceed", }, { name: "non-positive lifetime", mutate: func(c *Config) { c.ConnMaxLifetime = 0 }, wantSub: "conn max lifetime", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() cfg := DefaultConfig() cfg.PrimaryDSN = "postgres://localhost" tt.mutate(&cfg) err := cfg.Validate() if err == nil { t.Fatalf("expected validate error, got nil") } if !strings.Contains(err.Error(), tt.wantSub) { t.Fatalf("error %q does not contain %q", err, tt.wantSub) } }) } } func TestLoadFromEnvUsesDefaultsWhenOnlyPrimarySet(t *testing.T) { const prefix = "TESTSVC" t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "postgres://example/galaxy?sslmode=disable") cfg, err := LoadFromEnv(prefix) if err != nil { t.Fatalf("load from env: %v", err) } if cfg.PrimaryDSN != "postgres://example/galaxy?sslmode=disable" { t.Fatalf("primary DSN = %q", cfg.PrimaryDSN) } if len(cfg.ReplicaDSNs) != 0 { t.Fatalf("replica DSNs = %v, want empty", cfg.ReplicaDSNs) } if cfg.OperationTimeout != DefaultOperationTimeout { t.Fatalf("operation timeout = %v", cfg.OperationTimeout) } if cfg.MaxOpenConns != DefaultMaxOpenConns { t.Fatalf("max open conns = %d", cfg.MaxOpenConns) } } func TestLoadFromEnvParsesAllOverrides(t *testing.T) { const prefix = "TESTSVC" t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "postgres://example/galaxy?sslmode=disable") t.Setenv(prefix+"_POSTGRES_REPLICA_DSNS", "postgres://r1, postgres://r2 ,") t.Setenv(prefix+"_POSTGRES_OPERATION_TIMEOUT", "750ms") t.Setenv(prefix+"_POSTGRES_MAX_OPEN_CONNS", "40") t.Setenv(prefix+"_POSTGRES_MAX_IDLE_CONNS", "10") t.Setenv(prefix+"_POSTGRES_CONN_MAX_LIFETIME", "15m") cfg, err := LoadFromEnv(prefix) if err != nil { t.Fatalf("load from env: %v", err) } if got, want := cfg.OperationTimeout, 750*time.Millisecond; got != want { t.Fatalf("operation timeout = %v, want %v", got, want) } if got, want := cfg.MaxOpenConns, 40; got != want { t.Fatalf("max open conns = %d, want %d", got, want) } if got, want := cfg.MaxIdleConns, 10; got != want { t.Fatalf("max idle conns = %d, want %d", got, want) } if got, want := cfg.ConnMaxLifetime, 15*time.Minute; got != want { t.Fatalf("conn max lifetime = %v, want %v", got, want) } if got, want := len(cfg.ReplicaDSNs), 2; got != want { t.Fatalf("replica DSN count = %d, want %d", got, want) } if cfg.ReplicaDSNs[0] != "postgres://r1" || cfg.ReplicaDSNs[1] != "postgres://r2" { t.Fatalf("replica DSNs = %v", cfg.ReplicaDSNs) } } func TestLoadFromEnvFailsWhenPrimaryMissing(t *testing.T) { const prefix = "TESTSVC" t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "") if _, err := LoadFromEnv(prefix); err == nil { t.Fatal("expected error when primary DSN missing") } } func TestLoadFromEnvRejectsEmptyPrefix(t *testing.T) { t.Parallel() if _, err := LoadFromEnv(" "); err == nil { t.Fatal("expected error on empty prefix") } } func TestLoadFromEnvSurfacesDurationParseErrors(t *testing.T) { const prefix = "TESTSVC" t.Setenv(prefix+"_POSTGRES_PRIMARY_DSN", "postgres://example/galaxy") t.Setenv(prefix+"_POSTGRES_OPERATION_TIMEOUT", "not-a-duration") if _, err := LoadFromEnv(prefix); err == nil { t.Fatal("expected parse error") } else if !strings.Contains(err.Error(), "OPERATION_TIMEOUT") { t.Fatalf("error %q should name the env var", err) } }