fix(resilience): expose providerCooldown in GET+PATCH /api/resilience (#3590)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-10 15:34:33 -03:00
committed by GitHub
parent 39f7be320d
commit 7d7a45ae28
3 changed files with 83 additions and 0 deletions

View File

@@ -133,6 +133,7 @@ export async function GET() {
maxRetries: resilience.waitForCooldown.maxRetries,
maxRetryWaitSec: resilience.waitForCooldown.maxRetryWaitSec,
},
providerCooldown: resilience.providerCooldown,
legacy: buildLegacyResilienceCompat(resilience),
});
} catch (err: unknown) {
@@ -188,6 +189,12 @@ export async function PATCH(request) {
...(body.waitForCooldown
? { waitForCooldown: body.waitForCooldown as ResilienceSettingsPatch["waitForCooldown"] }
: {}),
...(body.providerCooldown
? {
providerCooldown:
body.providerCooldown as ResilienceSettingsPatch["providerCooldown"],
}
: {}),
...normalizeLegacyPatch(body),
});
@@ -222,6 +229,7 @@ export async function PATCH(request) {
maxRetries: nextResilience.waitForCooldown.maxRetries,
maxRetryWaitSec: nextResilience.waitForCooldown.maxRetryWaitSec,
},
providerCooldown: nextResilience.providerCooldown,
legacy: buildLegacyResilienceCompat(nextResilience),
});
} catch (err: unknown) {

View File

@@ -1161,6 +1161,14 @@ const waitForCooldownSettingsSchema = z
})
.strict();
const providerCooldownSettingsSchema = z
.object({
enabled: z.boolean().optional(),
minRetryCooldownMs: z.number().int().min(0).max(300000).optional(),
maxRetryCooldownMs: z.number().int().min(0).max(3600000).optional(),
})
.strict();
export const updateResilienceSchema = z
.object({
requestQueue: requestQueueSettingsSchema.optional(),
@@ -1179,6 +1187,7 @@ export const updateResilienceSchema = z
.strict()
.optional(),
waitForCooldown: waitForCooldownSettingsSchema.optional(),
providerCooldown: providerCooldownSettingsSchema.optional(),
profiles: z
.object({
oauth: legacyResilienceProfileSchema.optional(),
@@ -1195,6 +1204,7 @@ export const updateResilienceSchema = z
!value.connectionCooldown &&
!value.providerBreaker &&
!value.waitForCooldown &&
!value.providerCooldown &&
!value.profiles &&
!value.defaults
) {

View File

@@ -0,0 +1,65 @@
/**
* Regression test for #3556 / fix: providerCooldown missing from GET+PATCH /api/resilience
*
* The ResilienceTab component fetches GET /api/resilience and expects a `providerCooldown`
* field; likewise PATCH must accept and persist it. Without both, the Settings → Resilience
* page crashes on load and cannot save the provider-cooldown configuration.
*/
import assert from "node:assert/strict";
import { describe, it } from "node:test";
// Validate the schema accepts providerCooldown
import { updateResilienceSchema } from "../../src/shared/validation/schemas.js";
// Validate settings roundtrip
import {
resolveResilienceSettings,
mergeResilienceSettings,
} from "../../src/lib/resilience/settings.js";
describe("providerCooldown in updateResilienceSchema", () => {
it("accepts a valid providerCooldown patch", () => {
const result = updateResilienceSchema.safeParse({
providerCooldown: {
enabled: true,
minRetryCooldownMs: 3000,
maxRetryCooldownMs: 120000,
},
});
assert.equal(result.success, true, `Schema rejected valid patch: ${JSON.stringify(result)}`);
});
it("accepts partial providerCooldown patch", () => {
const result = updateResilienceSchema.safeParse({
providerCooldown: { enabled: false },
});
assert.equal(result.success, true, `Schema rejected partial patch: ${JSON.stringify(result)}`);
});
it("rejects unknown keys inside providerCooldown", () => {
const result = updateResilienceSchema.safeParse({
providerCooldown: { enabled: true, unknownKey: 42 },
});
assert.equal(result.success, false, "Schema should reject unknown keys");
});
});
describe("providerCooldown roundtrip through mergeResilienceSettings", () => {
it("merges providerCooldown overrides correctly", () => {
const base = resolveResilienceSettings({});
const merged = mergeResilienceSettings(base, {
providerCooldown: { enabled: true, minRetryCooldownMs: 2000 },
});
assert.equal(merged.providerCooldown.enabled, true);
assert.equal(merged.providerCooldown.minRetryCooldownMs, 2000);
// maxRetryCooldownMs should remain from default
assert.ok(merged.providerCooldown.maxRetryCooldownMs > 0);
});
it("resolveResilienceSettings returns providerCooldown field", () => {
const settings = resolveResilienceSettings({});
assert.ok("providerCooldown" in settings, "providerCooldown missing from resolved settings");
assert.ok("enabled" in settings.providerCooldown);
assert.ok("minRetryCooldownMs" in settings.providerCooldown);
assert.ok("maxRetryCooldownMs" in settings.providerCooldown);
});
});