98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
/**
|
|
* TDD for F5.1 (Combo U1b Slice 2) — summarizeConnectionCooldown: aggregates the
|
|
* per-connection cooldown state (`rateLimitedUntil`) into a per-provider summary the
|
|
* cascade can badge, exposed by GET /api/monitoring/health as `connectionHealth`.
|
|
*
|
|
* Parsing of rateLimitedUntil is delegated to cooldownUntilMs (#3954) — it accepts
|
|
* ISO strings, Date objects, AND numeric epoch strings (the SQLite TEXT-affinity case).
|
|
*
|
|
* Run: node --import tsx/esm --test tests/unit/monitoring/connection-cooldown-summary.test.ts
|
|
*/
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { summarizeConnectionCooldown } from "../../../src/lib/monitoring/observability.ts";
|
|
|
|
const NOW = 1_000_000_000_000;
|
|
|
|
describe("summarizeConnectionCooldown", () => {
|
|
it("returns an empty map for no connections", () => {
|
|
assert.deepEqual(summarizeConnectionCooldown([], NOW), {});
|
|
});
|
|
|
|
it("omits providers whose connections are all available (no future rateLimitedUntil)", () => {
|
|
const out = summarizeConnectionCooldown(
|
|
[
|
|
{ provider: "openai", rateLimitedUntil: null },
|
|
{ provider: "openai", rateLimitedUntil: new Date(NOW - 5000).toISOString() }, // past → expired
|
|
],
|
|
NOW
|
|
);
|
|
assert.deepEqual(out, {});
|
|
});
|
|
|
|
it("reports coolingDown / total / soonestRetryAfterMs for a provider with cooling connections", () => {
|
|
const out = summarizeConnectionCooldown(
|
|
[
|
|
{ provider: "anthropic", rateLimitedUntil: new Date(NOW + 28_000).toISOString() },
|
|
{ provider: "anthropic", rateLimitedUntil: new Date(NOW + 60_000).toISOString() },
|
|
{ provider: "anthropic", rateLimitedUntil: null }, // available
|
|
],
|
|
NOW
|
|
);
|
|
assert.ok(out.anthropic);
|
|
assert.equal(out.anthropic.coolingDown, 2);
|
|
assert.equal(out.anthropic.total, 3);
|
|
assert.equal(
|
|
out.anthropic.soonestRetryAfterMs,
|
|
28_000,
|
|
"soonest = the connection that recovers first"
|
|
);
|
|
});
|
|
|
|
it("parses numeric-epoch-string rateLimitedUntil (SQLite TEXT-affinity, #3954)", () => {
|
|
const out = summarizeConnectionCooldown(
|
|
[{ provider: "glm", rateLimitedUntil: String(NOW + 15_000) }],
|
|
NOW
|
|
);
|
|
assert.ok(out.glm);
|
|
assert.equal(out.glm.coolingDown, 1);
|
|
assert.equal(out.glm.soonestRetryAfterMs, 15_000);
|
|
});
|
|
|
|
it("accepts a raw epoch number too", () => {
|
|
const out = summarizeConnectionCooldown(
|
|
[{ provider: "glm", rateLimitedUntil: NOW + 9000 }],
|
|
NOW
|
|
);
|
|
assert.equal(out.glm?.soonestRetryAfterMs, 9000);
|
|
});
|
|
|
|
it("groups connections per provider independently", () => {
|
|
const out = summarizeConnectionCooldown(
|
|
[
|
|
{ provider: "openai", rateLimitedUntil: new Date(NOW + 10_000).toISOString() },
|
|
{ provider: "anthropic", rateLimitedUntil: new Date(NOW + 40_000).toISOString() },
|
|
{ provider: "openai", rateLimitedUntil: null },
|
|
],
|
|
NOW
|
|
);
|
|
assert.equal(out.openai.coolingDown, 1);
|
|
assert.equal(out.openai.total, 2);
|
|
assert.equal(out.anthropic.coolingDown, 1);
|
|
assert.equal(out.anthropic.total, 1);
|
|
});
|
|
|
|
it("ignores connections without a provider and never returns negative retry", () => {
|
|
const out = summarizeConnectionCooldown(
|
|
[
|
|
{ rateLimitedUntil: new Date(NOW + 5000).toISOString() }, // no provider
|
|
{ provider: "x", rateLimitedUntil: new Date(NOW + 1).toISOString() },
|
|
],
|
|
NOW
|
|
);
|
|
assert.equal(Object.keys(out).length, 1);
|
|
assert.ok(out.x.soonestRetryAfterMs >= 0);
|
|
});
|
|
});
|