-
Notifications
You must be signed in to change notification settings - Fork 3
fix(config,app)!: a delivered-empty numeric config value fails startup #1112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b98ecc0
9d4218c
0637778
da28b36
87b7156
ee15349
85db30d
2686a94
dd6aacb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -185,6 +185,74 @@ func TestConfigPerTenantJobKeys(t *testing.T) { | |
| }) | ||
| } | ||
|
|
||
| // TestLoadRejectsEmptyNumericEnv pins the delivered-empty rule for numeric keys: a | ||
| // set-but-empty variable used to decode as a legal 0 (defeating ADR-065's tri-state and | ||
| // silently zeroing byte limits), and now fails Load naming the key. | ||
| func TestLoadRejectsEmptyNumericEnv(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| envVar string | ||
| wantKey string | ||
| }{ | ||
| {name: "keystore_secretminlength", envVar: "KEYSTORE_SECRETMINLENGTH", wantKey: "keystore.secretminlength"}, | ||
| {name: "server_bodylimit", envVar: "SERVER_BODYLIMIT", wantKey: "server.bodylimit"}, | ||
| {name: "server_port", envVar: "SERVER_PORT", wantKey: "server.port"}, | ||
| // database.port is an ADR-051 identity key AND numeric, so the numeric guard | ||
| // reaches it first: it now fails at decode rather than with the identity error. | ||
| {name: "database_port_changes_error_class", envVar: "DATABASE_PORT", wantKey: "database.port"}, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| clearEnvironmentVariables() | ||
| t.Setenv(tt.envVar, "") | ||
|
|
||
| _, err := Load() | ||
|
|
||
| require.Error(t, err) | ||
| assert.ErrorContains(t, err, tt.wantKey) | ||
| assert.ErrorContains(t, err, "delivered empty") | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // TestLoadEmptyDurationEnvKeepsItsOwnError pins the guard's one exemption: time.Duration | ||
| // targets fall through to the duration parser, so an empty duration still fails with the | ||
| // parse error rather than the delivered-empty one. Both are loud; this pins which. | ||
| func TestLoadEmptyDurationEnvKeepsItsOwnError(t *testing.T) { | ||
| clearEnvironmentVariables() | ||
| t.Setenv("SERVER_TIMEOUT_READ", "") | ||
|
|
||
| _, err := Load() | ||
|
|
||
| require.Error(t, err) | ||
| assert.ErrorContains(t, err, "invalid duration") | ||
| assert.NotContains(t, err.Error(), "delivered empty", | ||
| "the duration parser owns this target; guarding it here would only change the message") | ||
| } | ||
|
|
||
| // TestLoadEmptyNumericYAMLStringRejected covers the same rule arriving through YAML: an | ||
| // empty string takes the identical decode path an empty env var does. | ||
| func TestLoadEmptyNumericYAMLStringRejected(t *testing.T) { | ||
| _, err := loadDeliveredEmptyFixture(t, "keystore:\n secretminlength: \"\"\n", nil) | ||
|
|
||
| require.Error(t, err) | ||
| assert.ErrorContains(t, err, "secretminlength") | ||
| assert.ErrorContains(t, err, "delivered empty") | ||
| } | ||
|
|
||
| // TestLoadYAMLNullNumericKeepsTodaysDecode pins the boundary the guard deliberately does | ||
| // NOT cover: a YAML null is different plumbing — koanf delivers a nil value, not the "" | ||
| // the guard judges — so a null pointer key still decodes as absent and takes its default. | ||
| // Documented in ADR-074; this test exists so the boundary cannot drift unnoticed. | ||
| func TestLoadYAMLNullNumericKeepsTodaysDecode(t *testing.T) { | ||
| cfg, err := loadDeliveredEmptyFixture(t, "keystore:\n secretminlength:\n", nil) | ||
|
|
||
| require.NoError(t, err) | ||
| require.NotNil(t, cfg.KeyStore.SecretMinLength) | ||
| assert.Equal(t, 32, *cfg.KeyStore.SecretMinLength, "a null key is absence, so the floor still applies") | ||
| } | ||
|
Comment on lines
+234
to
+254
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Clear
Add 🤖 Prompt for AI Agents |
||
|
|
||
| func TestLoadMultiElementStringSliceEnv(t *testing.T) { | ||
| clearEnvironmentVariables() | ||
| t.Setenv("SCHEDULER_SECURITY_CIDRALLOWLIST", "10.0.0.0/8,192.168.0.0/16") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Preserve cleanup errors on this startup failure path.
Line 137 discards
cacheManager.Close()errors. Lines 139-143 call a helper that also discards database and messaging close errors. If cleanup fails,dependenciesreturns only the observability decode error and can leave startup-created resources active.Return the cleanup errors with the initialization error, for example through
errors.Join. Change the cleanup callback to return an error so the injected test seam preserves the same contract.As per coding guidelines: “Handle errors idiomatically, wrap once at boundaries. No silent failures.” <coding_guidelines>
🤖 Prompt for AI Agents
Source: Coding guidelines