Files
2026-07-13 13:39:12 +08:00

115 lines
3.9 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
const {
trackPendingRequest,
getPendingById,
getPendingRequests,
sweepStalePendingRequests,
getMaxPendingRequestAgeMs,
clearPendingRequests,
} = await import("../../src/lib/usage/usageHistory.ts");
const MINUTE_MS = 60 * 1000;
const HOUR_MS = 60 * MINUTE_MS;
test("sweepStalePendingRequests evicts orphaned pending details and self-heals counts", () => {
clearPendingRequests();
// One request that will be treated as orphaned (never finalized), one fresh.
const staleId = trackPendingRequest("gpt-x", "openai", "conn-stale", true);
const freshId = trackPendingRequest("gpt-x", "openai", "conn-fresh", true);
assert.ok(staleId && freshId, "both started requests should produce ids");
assert.equal(getPendingById().size, 2);
assert.equal(getPendingRequests().byModel["gpt-x (openai)"], 2);
// Age the stale entry well beyond the max age.
const stale = getPendingById().get(staleId);
assert.ok(stale, "stale detail should exist");
stale.startedAt = Date.now() - 2 * HOUR_MS;
const removed = sweepStalePendingRequests(Date.now(), HOUR_MS);
assert.equal(removed, 1, "exactly one orphaned entry should be swept");
assert.equal(getPendingById().size, 1, "only the fresh entry should remain");
assert.ok(getPendingById().has(freshId), "fresh entry must survive");
// Counts must reflect the eviction (decremented, not left dangling).
assert.equal(getPendingRequests().byModel["gpt-x (openai)"], 1);
assert.equal(getPendingRequests().byAccount["conn-stale"], undefined);
assert.equal(getPendingRequests().byAccount["conn-fresh"]["gpt-x (openai)"], 1);
clearPendingRequests();
});
test("sweepStalePendingRequests is a no-op when nothing is stale", () => {
clearPendingRequests();
trackPendingRequest("m", "p", "c1", true);
trackPendingRequest("m", "p", "c2", true);
const removed = sweepStalePendingRequests(Date.now(), HOUR_MS);
assert.equal(removed, 0);
assert.equal(getPendingById().size, 2);
clearPendingRequests();
});
test("sweepStalePendingRequests defaults to a one hour max pending age", () => {
clearPendingRequests();
const staleId = trackPendingRequest("m", "p", "old", true);
const recentId = trackPendingRequest("m", "p", "recent", true);
assert.ok(staleId && recentId);
const now = Date.now();
const stale = getPendingById().get(staleId);
const recent = getPendingById().get(recentId);
assert.ok(stale && recent);
stale.startedAt = now - 61 * MINUTE_MS;
recent.startedAt = now - 59 * MINUTE_MS;
const removed = sweepStalePendingRequests(now);
assert.equal(removed, 1);
assert.equal(getPendingById().has(staleId), false);
assert.equal(getPendingById().has(recentId), true);
clearPendingRequests();
});
test("pending sweep max age can be overridden through environment", () => {
clearPendingRequests();
const previous = process.env.MAX_PENDING_REQUEST_AGE_MS;
process.env.MAX_PENDING_REQUEST_AGE_MS = String(2 * HOUR_MS);
try {
const requestId = trackPendingRequest("m", "p", "custom-age", true);
assert.ok(requestId);
const detail = getPendingById().get(requestId);
assert.ok(detail);
detail.startedAt = Date.now() - 90 * MINUTE_MS;
assert.equal(getMaxPendingRequestAgeMs(), 2 * HOUR_MS);
assert.equal(sweepStalePendingRequests(Date.now()), 0);
assert.equal(getPendingById().has(requestId), true);
} finally {
if (previous === undefined) delete process.env.MAX_PENDING_REQUEST_AGE_MS;
else process.env.MAX_PENDING_REQUEST_AGE_MS = previous;
clearPendingRequests();
}
});
test("invalid pending sweep max age falls back to one hour", () => {
const previous = process.env.MAX_PENDING_REQUEST_AGE_MS;
process.env.MAX_PENDING_REQUEST_AGE_MS = "not-a-number";
try {
assert.equal(getMaxPendingRequestAgeMs(), HOUR_MS);
} finally {
if (previous === undefined) delete process.env.MAX_PENDING_REQUEST_AGE_MS;
else process.env.MAX_PENDING_REQUEST_AGE_MS = previous;
}
});