import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; // ── 1. FETCH_BODY_TIMEOUT_MS constant export validation ────────────────── test("FETCH_BODY_TIMEOUT_MS is exported from open-sse/config/constants and is a positive number", async () => { const constants = await import("../../open-sse/config/constants.ts"); assert.ok("FETCH_BODY_TIMEOUT_MS" in constants, "FETCH_BODY_TIMEOUT_MS should be exported"); assert.equal(typeof constants.FETCH_BODY_TIMEOUT_MS, "number"); assert.ok(constants.FETCH_BODY_TIMEOUT_MS > 0, "should be a positive number"); }); test("FETCH_BODY_TIMEOUT_MS defaults to FETCH_TIMEOUT_MS when no env override", async () => { const constants = await import("../../open-sse/config/constants.ts"); assert.equal( constants.FETCH_BODY_TIMEOUT_MS, constants.FETCH_TIMEOUT_MS, "FETCH_BODY_TIMEOUT_MS should default to FETCH_TIMEOUT_MS" ); }); // ── 2. BodyTimeoutError classification in chatCore ────────────────────── test("chatCore error classification maps BodyTimeoutError to 504 GATEWAY_TIMEOUT", () => { // Read the source to verify the error classification logic includes BodyTimeoutError const content = fs.readFileSync("open-sse/handlers/chatCore.ts", "utf8"); // The error classification block should include BodyTimeoutError alongside TimeoutError const classificationPattern = /error\.name === ["']TimeoutError["']\s*\|\|\s*error\.name === ["']BodyTimeoutError["']/; assert.ok( classificationPattern.test(content), "chatCore should classify BodyTimeoutError as GATEWAY_TIMEOUT (504)" ); }); test("chatCore catch block decrements pending requests for all error types", () => { const content = fs.readFileSync("open-sse/handlers/chatCore.ts", "utf8"); // The catch block should call trackPendingRequest with false before error classification const catchBlockPattern = /catch\s*\(error\)\s*\{[^}]*trackPendingRequest\([^)]*,\s*false\)/; assert.ok( catchBlockPattern.test(content), "chatCore catch block should decrement pending requests" ); }); test("withBodyTimeout error name is BodyTimeoutError", async () => { const { withBodyTimeout } = await import("../../open-sse/utils/stream.ts"); const neverResolves = new Promise(() => {}); try { await withBodyTimeout(neverResolves, 20); assert.fail("should have thrown"); } catch (error) { assert.ok(error instanceof Error); assert.equal(error.name, "BodyTimeoutError"); } }); // ── 3. BodyTimeoutError triggers correct pending request decrement ────── test("BodyTimeoutError from withBodyTimeout does not leave timer leaks", async () => { const { withBodyTimeout } = await import("../../open-sse/utils/stream.ts"); // Fire multiple short timeouts to ensure no timer accumulation const promises = Array.from({ length: 10 }, () => withBodyTimeout(new Promise(() => {}), 10).catch(() => {}) ); await Promise.all(promises); // Wait a bit to ensure all timers are cleaned up await new Promise((resolve) => setTimeout(resolve, 100)); // If timers leaked, the process would hang or show warnings — this test // passing confirms proper cleanup under concurrent timeout scenarios. assert.ok(true, "all timers cleaned up successfully"); }); // ── 4. clearPendingRequests + trackPendingRequest integration ──────────── test("trackPendingRequest followed by clearPendingRequests zeroes all counts", async () => { const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); usageHistory.trackPendingRequest("m1", "p1", "c1", true); usageHistory.trackPendingRequest("m1", "p1", "c1", true); usageHistory.trackPendingRequest("m2", "p2", "c2", true); const before = usageHistory.getPendingRequests(); assert.equal(before.byModel["m1 (p1)"], 2); assert.equal(before.byModel["m2 (p2)"], 1); usageHistory.clearPendingRequests(); const after = usageHistory.getPendingRequests(); assert.equal(Object.keys(after.byModel).length, 0); assert.equal(Object.keys(after.byAccount).length, 0); assert.equal(Object.keys(after.details).length, 0); }); test("clearPendingRequests allows subsequent tracking to work correctly", async () => { const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); usageHistory.trackPendingRequest("m1", "p1", "c1", true); usageHistory.clearPendingRequests(); // After clearing, tracking should increment from 0 usageHistory.trackPendingRequest("m3", "p3", "c3", true); usageHistory.trackPendingRequest("m3", "p3", "c3", true); const pending = usageHistory.getPendingRequests(); assert.equal(pending.byModel["m3 (p3)"], 2); assert.ok(pending.details["c3"]); // Decrement should also work usageHistory.trackPendingRequest("m3", "p3", "c3", false); assert.equal(pending.byModel["m3 (p3)"], 1); // Final decrement should clear details usageHistory.trackPendingRequest("m3", "p3", "c3", false); assert.equal(pending.byModel["m3 (p3)"], 0); assert.equal(pending.details["c3"], undefined); });