feat(relay): add policy overrides and idempotency-aware retry gating

This commit is contained in:
2026-03-24 13:03:49 +00:00
parent 1ba0cbe8a9
commit bb190946ae
3 changed files with 263 additions and 13 deletions
@@ -369,6 +369,152 @@ test("forwardGetData telemetry request id is stable across lifecycle events", as
assert.strictEqual(new Set(relayRequestIds).size, 1);
});
test("forwardGetData relayPolicy override controls retry behavior", async () => {
let callCount = 0;
const infoLogs = [];
const mod = loadRelayForwardingModule({
axios: {
get: async () => {
callCount += 1;
if (callCount < 3) {
const error = new Error("temporary outage");
error.response = { status: 503 };
throw error;
}
return { data: { ok: true }, status: 200 };
}
},
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=abc",
azureHeaders: () => ({ headers: {} }),
redactSensitive: (value) => value,
consoleLogger: () => {},
console: {
log: () => {},
info: (...args) => infoLogs.push(args),
warn: () => {},
error: () => {}
}
});
const result = await mod.forwardGetData({
queryUrl: "incidents?$top=1",
maxRetries: 0,
relayPolicy: {
maxRetries: 2,
retryBaseDelayMs: 0,
retryMaxDelayMs: 0,
method: "GET"
}
});
assert.strictEqual(callCount, 3);
assert.deepStrictEqual(result.data, { ok: true });
const startEvent = infoLogs.find(
(entry) => entry[0] === "relay_request_started"
);
assert.ok(startEvent);
assert.strictEqual(startEvent[1].maxRetries, 2);
assert.strictEqual(startEvent[1].method, "GET");
});
test("forwardGetData blocks retries for non-idempotent methods by default", async () => {
let callCount = 0;
const infoLogs = [];
const mod = loadRelayForwardingModule({
axios: {
get: async () => {
callCount += 1;
const error = new Error("temporary outage");
error.response = { status: 503 };
throw error;
}
},
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=abc",
azureHeaders: () => ({ headers: {} }),
redactSensitive: (value) => value,
consoleLogger: () => {},
console: {
log: () => {},
info: (...args) => infoLogs.push(args),
warn: () => {},
error: () => {}
}
});
let thrown = null;
try {
await mod.forwardGetData({
queryUrl: "incidents?$top=1",
relayPolicy: {
method: "POST",
maxRetries: 3,
retryBaseDelayMs: 0,
retryMaxDelayMs: 0
}
});
} catch (error) {
thrown = error;
}
assert.ok(thrown);
assert.strictEqual(callCount, 1);
const retryEvents = infoLogs.filter(
(entry) => entry[0] === "relay_request_retrying"
);
assert.strictEqual(retryEvents.length, 0);
const failedEvent = infoLogs.find(
(entry) => entry[0] === "relay_request_failed"
);
assert.ok(failedEvent);
assert.strictEqual(failedEvent[1].method, "POST");
});
test("forwardGetData can allow retries for non-idempotent methods when explicitly enabled", async () => {
let callCount = 0;
const mod = loadRelayForwardingModule({
axios: {
get: async () => {
callCount += 1;
if (callCount === 1) {
const error = new Error("temporary outage");
error.response = { status: 503 };
throw error;
}
return { data: { ok: true }, status: 200 };
}
},
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=abc",
azureHeaders: () => ({ headers: {} }),
redactSensitive: (value) => value,
consoleLogger: () => {}
});
const result = await mod.forwardGetData({
queryUrl: "incidents?$top=1",
relayPolicy: {
method: "PATCH",
maxRetries: 1,
retryBaseDelayMs: 0,
retryMaxDelayMs: 0,
allowNonIdempotentRetries: true
}
});
assert.strictEqual(callCount, 2);
assert.deepStrictEqual(result.data, { ok: true });
});
const run = async () => {
let passed = 0;
for (const currentTest of tests) {