mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 04:12:17 +03:00
Two shards on release/v3.8.51 went red in one day with the same signature —
"ENOTEMPTY, Directory not empty: /tmp/omniroute-<test>-XXXXXX" — from
combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only
.github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass
alone and on re-run: the cleanup races something still writing into the directory
(SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner
the window opens. 1154 test files do their own cleanup with
fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries.
One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record):
every rm / rmSync / rmdirSync option object with `recursive: true` and no
`maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries
ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292
files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included.
Only the option object changes: no call site, assertion or import is touched.
Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292
files; a random 20-file sample runs green (quota-redis-store hangs identically on
the untouched tree — it needs a Redis on localhost, an environment matter). The
four unit shards on this PR are the full run.
741 lines
28 KiB
TypeScript
741 lines
28 KiB
TypeScript
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";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-reqlogger-ep-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const usageHistory = await import("../../src/lib/usage/usageHistory.ts");
|
|
const callLogs = await import("../../src/lib/usage/callLogs.ts");
|
|
|
|
test.after(() => {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
});
|
|
|
|
// Captured stream chunks are prefixed with a per-chunk arrival timestamp
|
|
// ("[HH:MM:SS.mmm] ") by the request logger for streaming-latency observability
|
|
// (added in #5834 / correlation-id observability). Strip it before comparing the
|
|
// raw payload so these assertions verify the chunk content + ordering, not the clock.
|
|
const stripChunkTs = (chunk: string): string => chunk.replace(/^\[\d{2}:\d{2}:\d{2}\.\d{3}\] /, "");
|
|
|
|
// ─── Active log endpoint removal ─────────────────────────────────────────
|
|
|
|
test("/api/logs/active route is removed", () => {
|
|
assert.equal(
|
|
fs.existsSync("src/app/api/logs/active/route.ts"),
|
|
false,
|
|
"active logs must not expose an in-memory timing-sensitive endpoint"
|
|
);
|
|
});
|
|
|
|
test("RequestLoggerV2 detail fetch uses /api/logs/[id] and not /api/logs/active", () => {
|
|
const content = fs.readFileSync("src/shared/components/RequestLoggerV2.tsx", "utf8");
|
|
assert.match(content, /fetch\(`\/api\/logs\/\$\{[^}]+\.id\}`,\s*\{\s*cache:\s*"no-store"/);
|
|
assert.doesNotMatch(content, /fetch\(["'`]\/api\/logs\/active/);
|
|
assert.match(content, /detailState:\s*"pending"/);
|
|
});
|
|
|
|
// ─── usageHistory module behaviour ───────────────────────────────────────
|
|
|
|
test("trackPendingRequest creates a detail entry", () => {
|
|
usageHistory.clearPendingRequests();
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", true, {
|
|
clientRequest: { messages: [{ role: "user", content: "hi" }] },
|
|
providerRequest: { model: "gpt-4" },
|
|
providerUrl: "https://api.openai.com/v1/chat/completions",
|
|
});
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const detail = pending.details["conn-1"]?.["gpt-4 (openai)"]?.[0];
|
|
assert.ok(detail, "should create detail entry");
|
|
assert.equal(detail.model, "gpt-4");
|
|
assert.equal(detail.provider, "openai");
|
|
assert.equal(detail.connectionId, "conn-1");
|
|
assert.ok(detail.id, "should have an id");
|
|
assert.ok(detail.startedAt > 0, "should have startedAt timestamp");
|
|
assert.ok(detail.clientRequest, "should preserve clientRequest");
|
|
assert.equal(detail.clientRequest.messages[0].content, "hi");
|
|
});
|
|
|
|
test("trackPendingRequest decrements and removes detail on finish", () => {
|
|
usageHistory.clearPendingRequests();
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", true);
|
|
assert.equal(usageHistory.getPendingRequests().details["conn-1"]?.["gpt-4 (openai)"]?.length, 1);
|
|
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", false);
|
|
const after = usageHistory.getPendingRequests();
|
|
assert.equal(after.details["conn-1"]?.["gpt-4 (openai)"]?.length ?? 0, 0);
|
|
});
|
|
|
|
test("trackPendingRequest does not go negative", () => {
|
|
usageHistory.clearPendingRequests();
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", false);
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", false);
|
|
const pending = usageHistory.getPendingRequests();
|
|
assert.equal(pending.byModel["gpt-4 (openai)"], 0);
|
|
});
|
|
|
|
test("updatePendingRequestStreamChunks stores stream chunks in the detail", () => {
|
|
usageHistory.clearPendingRequests();
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", true);
|
|
|
|
const chunks = { provider: ['data: {"a":1}'], openai: [], client: [] };
|
|
usageHistory.updatePendingRequestStreamChunks("gpt-4", "openai", "conn-1", chunks);
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const detail = pending.details["conn-1"]?.["gpt-4 (openai)"]?.[0];
|
|
assert.ok(detail.streamChunks, "streamChunks should be set");
|
|
assert.equal(detail.streamChunks.provider.length, 1);
|
|
assert.equal(detail.streamChunks.provider[0], 'data: {"a":1}');
|
|
});
|
|
|
|
test("updatePendingRequest keeps pending detail API view in sync", () => {
|
|
usageHistory.clearPendingRequests();
|
|
const requestId = usageHistory.trackPendingRequest(
|
|
"claude-sonnet-4-6",
|
|
"cc-test",
|
|
"conn-1",
|
|
true,
|
|
{
|
|
clientRequest: { model: "cc-test/claude-sonnet-4-6", reasoning_effort: "xhigh" },
|
|
providerRequest: { model: "claude-sonnet-4-6", reasoning_effort: "xhigh" },
|
|
}
|
|
);
|
|
assert.ok(requestId, "trackPendingRequest should return an id");
|
|
|
|
usageHistory.updatePendingRequest("claude-sonnet-4-6", "cc-test", "conn-1", {
|
|
providerRequest: { model: "claude-sonnet-4-6", reasoning_effort: "high" },
|
|
stage: "provider_response_started",
|
|
});
|
|
|
|
const modelKey = "claude-sonnet-4-6 (cc-test)";
|
|
const detailFromQueue = usageHistory.getPendingRequests().details["conn-1"]?.[modelKey]?.[0];
|
|
const detailFromId = usageHistory.getPendingById().get(requestId);
|
|
|
|
assert.equal(detailFromId, detailFromQueue);
|
|
assert.deepEqual(detailFromId?.providerRequest, {
|
|
model: "claude-sonnet-4-6",
|
|
reasoning_effort: "high",
|
|
});
|
|
assert.deepEqual(detailFromId?.clientRequest, {
|
|
model: "cc-test/claude-sonnet-4-6",
|
|
reasoning_effort: "xhigh",
|
|
});
|
|
});
|
|
|
|
test("updatePendingRequestById updates and finalizes the exact overlapping request", () => {
|
|
usageHistory.clearPendingRequests();
|
|
const firstId = usageHistory.trackPendingRequest("claude-sonnet-4-6", "cc-test", "conn-1", true, {
|
|
providerRequest: { request: "first", reasoning_effort: "xhigh" },
|
|
});
|
|
const secondId = usageHistory.trackPendingRequest(
|
|
"claude-sonnet-4-6",
|
|
"cc-test",
|
|
"conn-1",
|
|
true,
|
|
{
|
|
providerRequest: { request: "second", reasoning_effort: "xhigh" },
|
|
}
|
|
);
|
|
assert.ok(firstId);
|
|
assert.ok(secondId);
|
|
|
|
const updated = usageHistory.updatePendingRequestById(firstId, {
|
|
providerRequest: { request: "first", reasoning_effort: "high" },
|
|
stage: "sending_to_provider",
|
|
});
|
|
assert.equal(updated, true);
|
|
|
|
const modelKey = "claude-sonnet-4-6 (cc-test)";
|
|
const details = usageHistory.getPendingRequests().details["conn-1"]?.[modelKey];
|
|
assert.equal(details?.length, 2);
|
|
assert.deepEqual(details?.[0]?.providerRequest, {
|
|
request: "first",
|
|
reasoning_effort: "high",
|
|
});
|
|
assert.deepEqual(details?.[1]?.providerRequest, {
|
|
request: "second",
|
|
reasoning_effort: "xhigh",
|
|
});
|
|
|
|
const completed = usageHistory.finalizePendingRequestById(firstId, {
|
|
clientResponse: { ok: true },
|
|
});
|
|
assert.equal(completed, true);
|
|
assert.deepEqual(usageHistory.getCompletedDetails().get(firstId)?.providerRequest, {
|
|
request: "first",
|
|
reasoning_effort: "high",
|
|
});
|
|
assert.equal(usageHistory.getPendingById().has(secondId), true);
|
|
});
|
|
|
|
test("updatePendingRequestStreamChunks stores empty streamChunks object (not null)", () => {
|
|
usageHistory.clearPendingRequests();
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "conn-1", true);
|
|
|
|
// Call with empty arrays (as pushStreamChunks does before data flows)
|
|
const empty = { provider: [], openai: [], client: [] };
|
|
usageHistory.updatePendingRequestStreamChunks("gpt-4", "openai", "conn-1", empty);
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const detail = pending.details["conn-1"]?.["gpt-4 (openai)"]?.[0];
|
|
assert.ok(detail.streamChunks, "streamChunks should be set even when empty");
|
|
assert.deepEqual(detail.streamChunks, { provider: [], openai: [], client: [] });
|
|
|
|
// Verify the reference is live: mutations to the original object are visible
|
|
empty.provider.push("data: hello");
|
|
assert.equal(detail.streamChunks.provider.length, 1);
|
|
assert.equal(detail.streamChunks.provider[0], "data: hello");
|
|
});
|
|
|
|
test("clearPendingRequests resets all counts and details", () => {
|
|
usageHistory.clearPendingRequests();
|
|
usageHistory.trackPendingRequest("m1", "p1", "c1", true);
|
|
usageHistory.trackPendingRequest("m2", "p2", "c2", true);
|
|
assert.equal(Object.keys(usageHistory.getPendingRequests().byModel).length, 2);
|
|
|
|
usageHistory.clearPendingRequests();
|
|
const pending = usageHistory.getPendingRequests();
|
|
assert.equal(Object.keys(pending.byModel).length, 0);
|
|
assert.equal(Object.keys(pending.byAccount).length, 0);
|
|
assert.equal(Object.keys(pending.details).length, 0);
|
|
});
|
|
|
|
// ─── Pending request data remains available for internal usage stats ─────
|
|
|
|
test("pending request detail shape remains available internally", () => {
|
|
usageHistory.clearPendingRequests();
|
|
usageHistory.trackPendingRequest("claude-3-opus", "anthropic", "conn-2", true, {
|
|
clientRequest: { messages: [{ role: "user", content: "hello" }] },
|
|
providerRequest: { model: "claude-3-opus" },
|
|
providerUrl: "https://api.anthropic.com/v1/messages",
|
|
});
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const entries = Object.entries(pending.details).flatMap(([connectionId, models]) =>
|
|
Object.entries(models).flatMap(([modelKey, details]) =>
|
|
details.map((detail) => ({
|
|
id: detail.id,
|
|
model: detail.model,
|
|
provider: detail.provider,
|
|
connectionId,
|
|
startedAt: detail.startedAt,
|
|
clientRequest: detail.clientRequest ?? null,
|
|
providerRequest: detail.providerRequest ?? null,
|
|
providerUrl: detail.providerUrl ?? null,
|
|
streamChunks: detail.streamChunks ?? null,
|
|
}))
|
|
)
|
|
);
|
|
|
|
assert.equal(entries.length, 1);
|
|
const row = entries[0];
|
|
assert.ok(row.id);
|
|
assert.equal(row.model, "claude-3-opus");
|
|
assert.equal(row.provider, "anthropic");
|
|
assert.equal(row.connectionId, "conn-2");
|
|
assert.ok(row.startedAt > 0);
|
|
assert.ok(row.clientRequest, "clientRequest should be present");
|
|
assert.ok(row.providerRequest, "providerRequest should be present");
|
|
assert.ok(row.providerUrl, "providerUrl should be present");
|
|
assert.equal(row.streamChunks, null, "streamChunks should be null initially");
|
|
});
|
|
|
|
// ─── /api/usage/call-logs/[id] route structure ────────────────────────────
|
|
|
|
test("GET /api/usage/call-logs/[id] route exists and uses auth", () => {
|
|
const content = fs.readFileSync("src/app/api/usage/call-logs/[id]/route.ts", "utf8");
|
|
assert.ok(content.includes("export async function GET"), "should export GET handler");
|
|
assert.ok(content.includes("requireManagementAuth"), "should check auth");
|
|
assert.ok(content.includes("getCallLogById"), "should import getCallLogById");
|
|
});
|
|
|
|
test("GET /api/usage/call-logs includes completed in-memory fallback rows", async () => {
|
|
const { buildCallLogListRows } = await import("../../src/app/api/usage/call-logs/route.ts");
|
|
usageHistory.clearPendingRequests();
|
|
|
|
const requestId = usageHistory.trackPendingRequest("gpt-4", "openai", "completed-conn-1", true, {
|
|
clientEndpoint: "/v1/chat/completions",
|
|
});
|
|
assert.ok(requestId);
|
|
|
|
const completed = usageHistory.finalizePendingRequestById(requestId, {
|
|
clientResponse: { choices: [{ message: { content: "done" } }] },
|
|
});
|
|
assert.equal(completed, true);
|
|
|
|
const completedDetail = usageHistory.getCompletedDetails().get(requestId);
|
|
assert.ok(completedDetail);
|
|
const rows = buildCallLogListRows({
|
|
logs: [],
|
|
connections: [{ id: "completed-conn-1", displayName: "Completed Account" }],
|
|
pendingDetails: usageHistory.getPendingById().values(),
|
|
completedDetails: usageHistory.getCompletedDetails().values(),
|
|
now: completedDetail!.startedAt + 60_000,
|
|
});
|
|
|
|
assert.equal(rows.length, 1);
|
|
assert.equal(rows[0].id, requestId);
|
|
assert.equal(rows[0].detailState, "in-memory");
|
|
assert.equal(rows[0].completed, true);
|
|
assert.equal(rows[0].account, "Completed Account");
|
|
assert.equal(rows[0].duration, completedDetail!.durationMs);
|
|
});
|
|
|
|
test("GET /api/usage/call-logs rows are ordered by priority then newest-first", async () => {
|
|
const { buildCallLogListRows } = await import("../../src/app/api/usage/call-logs/route.ts");
|
|
const rows = buildCallLogListRows({
|
|
logs: [
|
|
{
|
|
id: "persisted-newest",
|
|
timestamp: new Date(3000).toISOString(),
|
|
active: false,
|
|
},
|
|
{
|
|
id: "persisted-oldest",
|
|
timestamp: new Date(1000).toISOString(),
|
|
active: false,
|
|
},
|
|
],
|
|
connections: [],
|
|
pendingDetails: [],
|
|
completedDetails: [
|
|
{
|
|
id: "completed-middle",
|
|
model: "gpt-4",
|
|
provider: "openai",
|
|
connectionId: "conn",
|
|
startedAt: 2000,
|
|
completedAt: 2500,
|
|
durationMs: 500,
|
|
},
|
|
],
|
|
now: 4000,
|
|
});
|
|
|
|
// Rows sort by priority first (active=0 > completed in-memory=1 > persisted=2),
|
|
// then newest-first within each priority (#5834 correlation-id observability).
|
|
// The completed in-memory entry therefore floats above the persisted DB rows,
|
|
// which are ordered newest-first among themselves.
|
|
assert.deepEqual(
|
|
rows.map((row) => row.id),
|
|
["completed-middle", "persisted-newest", "persisted-oldest"]
|
|
);
|
|
});
|
|
|
|
test("getCallLogById returns null for unknown id", async () => {
|
|
const log = await callLogs.getCallLogById("nonexistent-id-12345");
|
|
assert.equal(log, null);
|
|
});
|
|
|
|
// ─── Merged view data shape ────────────────────────────────────────────
|
|
|
|
test("normalized active row has expected fields for the grid view", () => {
|
|
const rawRow = {
|
|
id: "test-id-1",
|
|
model: "gpt-4",
|
|
provider: "openai",
|
|
account: "conn-1",
|
|
startedAt: Date.now() - 5000,
|
|
runningTimeMs: 5000,
|
|
stage: "streaming",
|
|
stageUpdatedAt: Date.now(),
|
|
clientRequest: { messages: [] },
|
|
providerRequest: { model: "gpt-4" },
|
|
providerUrl: "https://api.openai.com/v1",
|
|
streamChunks: null,
|
|
};
|
|
|
|
const normalized = {
|
|
active: true,
|
|
id: rawRow.id,
|
|
model: rawRow.model,
|
|
provider: rawRow.provider,
|
|
account: rawRow.account,
|
|
timestamp: new Date(rawRow.startedAt).toISOString(),
|
|
duration: Math.max(0, Date.now() - rawRow.startedAt),
|
|
status: 0,
|
|
sourceFormat: null,
|
|
tokens: null,
|
|
comboName: null,
|
|
apiKeyName: null,
|
|
apiKeyId: null,
|
|
cacheSource: null,
|
|
requestedModel: null,
|
|
stage: rawRow.stage,
|
|
stageUpdatedAt: rawRow.stageUpdatedAt,
|
|
_activeRow: rawRow,
|
|
};
|
|
|
|
assert.equal(normalized.active, true);
|
|
assert.equal(normalized.id, "test-id-1");
|
|
assert.equal(normalized.model, "gpt-4");
|
|
assert.equal(normalized.provider, "openai");
|
|
assert.equal(normalized.status, 0);
|
|
assert.ok(normalized.duration >= 5000);
|
|
assert.ok(normalized.duration < 60000);
|
|
assert.equal(normalized.tokens, null);
|
|
assert.equal(normalized.cacheSource, null);
|
|
assert.equal(normalized.stage, "streaming");
|
|
});
|
|
|
|
// ─── requestLoggerSignature module ────────────────────────────────────────
|
|
|
|
const sigMod = await import("../../src/shared/components/requestLoggerSignature.ts");
|
|
|
|
test("resolveInitialVisibility returns true when document is undefined (SSR)", () => {
|
|
assert.equal(sigMod.resolveInitialVisibility(), true);
|
|
});
|
|
|
|
test("shouldAutoRefresh returns true when recording and on first page", () => {
|
|
assert.equal(sigMod.shouldAutoRefresh(true, 50, 50), true);
|
|
assert.equal(sigMod.shouldAutoRefresh(true, 25, 50), true);
|
|
});
|
|
|
|
test("shouldAutoRefresh returns false when not recording or past first page", () => {
|
|
assert.equal(sigMod.shouldAutoRefresh(false, 50, 50), false);
|
|
assert.equal(sigMod.shouldAutoRefresh(true, 100, 50), false);
|
|
assert.equal(sigMod.shouldAutoRefresh(false, 100, 50), false);
|
|
});
|
|
|
|
test("computeLogsSignature produces different signatures for different data", () => {
|
|
const a = sigMod.computeLogsSignature([
|
|
{ id: "1", status: 200, duration: 100, tokens: { out: 50 } },
|
|
]);
|
|
const b = sigMod.computeLogsSignature([
|
|
{ id: "1", status: 200, duration: 150, tokens: { out: 50 } },
|
|
]);
|
|
assert.notEqual(a, b, "different duration should change signature");
|
|
});
|
|
|
|
test("computeLogsSignature returns empty string for non-array input", () => {
|
|
assert.equal(sigMod.computeLogsSignature(null), "");
|
|
assert.equal(sigMod.computeLogsSignature(undefined), "");
|
|
assert.equal(sigMod.computeLogsSignature({}), "");
|
|
});
|
|
|
|
// ─── Duration live computation ───────────────────────────────────────────
|
|
|
|
test("duration is computed from startedAt and grows over time", () => {
|
|
const startedAt = Date.now() - 3000;
|
|
const duration = Math.max(0, Date.now() - startedAt);
|
|
assert.ok(duration >= 3000, "duration should be at least 3s");
|
|
assert.ok(duration < 60000, "duration should be within reason");
|
|
});
|
|
|
|
// ─── Navigation logic ────────────────────────────────────────────────────
|
|
|
|
test("handlePrev at first item closes modal (no wrap-around)", () => {
|
|
const items = [
|
|
{ id: "a", active: false },
|
|
{ id: "b", active: false },
|
|
{ id: "c", active: false },
|
|
];
|
|
const selectedId = "a";
|
|
const idx = items.findIndex((l) => l.id === selectedId);
|
|
assert.equal(idx, 0);
|
|
|
|
// handlePrev should NOT wrap to last item
|
|
if (idx > 0) {
|
|
assert.equal(items[idx - 1].id, "should not reach");
|
|
} else {
|
|
// closes modal
|
|
assert.ok(true, "prev at first item closes modal");
|
|
}
|
|
});
|
|
|
|
test("handlePrev navigates backward when not at first item", () => {
|
|
const items = [
|
|
{ id: "a", active: false },
|
|
{ id: "b", active: false },
|
|
{ id: "c", active: false },
|
|
];
|
|
const selectedId = "b";
|
|
const idx = items.findIndex((l) => l.id === selectedId);
|
|
assert.equal(idx, 1);
|
|
|
|
if (idx > 0) {
|
|
assert.equal(items[idx - 1].id, "a", "should navigate to previous item");
|
|
}
|
|
});
|
|
|
|
test("handleNext at last item closes modal (no wrap-around)", () => {
|
|
const items = [
|
|
{ id: "a", active: false },
|
|
{ id: "b", active: false },
|
|
];
|
|
const selectedId = "b";
|
|
const idx = items.findIndex((l) => l.id === selectedId);
|
|
assert.equal(idx, items.length - 1);
|
|
|
|
// handleNext at last item should close
|
|
if (idx < items.length - 1) {
|
|
assert.equal(items[idx + 1].id, "should not reach");
|
|
} else {
|
|
assert.ok(true, "next at last item closes modal");
|
|
}
|
|
});
|
|
|
|
test("handleNext navigates forward when not at last item", () => {
|
|
const items = [
|
|
{ id: "a", active: false },
|
|
{ id: "b", active: false },
|
|
];
|
|
const selectedId = "a";
|
|
const idx = items.findIndex((l) => l.id === selectedId);
|
|
|
|
if (idx < items.length - 1) {
|
|
assert.equal(items[idx + 1].id, "b", "should navigate to next item");
|
|
}
|
|
});
|
|
|
|
// ─── Detail endpoint polling ─────────────────────────────────────────────
|
|
|
|
test("deep-linked missing request id is kept pending for /api/logs/[id] polling", () => {
|
|
const content = fs.readFileSync("src/shared/components/RequestLoggerV2.tsx", "utf8");
|
|
assert.match(content, /pendingLookup:\s*true/);
|
|
// A deep-linked id that 404s while still active is kept pending...
|
|
assert.match(content, /setDetailData\(\{\s*detailState:\s*"pending"\s*\}\)/);
|
|
// ...and the detail-polling effect re-runs on that pending state.
|
|
assert.match(content, /detailData\?\.detailState/);
|
|
assert.doesNotMatch(content, /activeRequests|completedRows|completedRow/);
|
|
});
|
|
|
|
// ─── End-to-end streamChunks integration ──────────────────────────────────
|
|
|
|
test("createRequestLogger with connectionId/model/provider populates streamChunks on pending request", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
usageHistory.clearPendingRequests();
|
|
|
|
// track request so the pending detail entry exists
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-1", true, {
|
|
clientRequest: { messages: [{ role: "user", content: "hi" }] },
|
|
});
|
|
|
|
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
|
|
enabled: true,
|
|
captureStreamChunks: true,
|
|
connectionId: "test-conn-1",
|
|
model: "gpt-4",
|
|
provider: "openai",
|
|
});
|
|
|
|
// append a provider chunk — this should call pushStreamChunks() → updatePendingRequestStreamChunks()
|
|
logger.appendProviderChunk('data: {"content":"hello"}');
|
|
logger.appendProviderChunk('data: {"content":" world"}');
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const detail = pending.details["test-conn-1"]?.["gpt-4 (openai)"]?.[0];
|
|
|
|
assert.ok(detail, "pending request detail should exist");
|
|
assert.ok(detail.streamChunks, "streamChunks should be non-null after appendProviderChunk");
|
|
assert.ok(Array.isArray(detail.streamChunks.provider), "provider array should exist");
|
|
assert.equal(detail.streamChunks.provider.length, 2, "should have 2 provider chunks");
|
|
assert.equal(stripChunkTs(detail.streamChunks.provider[0]), 'data: {"content":"hello"}');
|
|
assert.equal(stripChunkTs(detail.streamChunks.provider[1]), 'data: {"content":" world"}');
|
|
assert.deepEqual(detail.streamChunks.openai, []);
|
|
assert.deepEqual(detail.streamChunks.client, []);
|
|
});
|
|
|
|
test("createRequestLogger requestId binds streamChunks to the exact pending request", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
usageHistory.clearPendingRequests();
|
|
|
|
const firstId = usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-a", true);
|
|
const secondId = usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-b", true);
|
|
|
|
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
|
|
enabled: true,
|
|
captureStreamChunks: true,
|
|
requestId: secondId,
|
|
});
|
|
|
|
logger.appendProviderChunk('data: {"content":"second"}');
|
|
|
|
const first = usageHistory.getPendingById().get(firstId);
|
|
const second = usageHistory.getPendingById().get(secondId);
|
|
|
|
assert.equal(first?.streamChunks, undefined);
|
|
assert.equal(stripChunkTs(second?.streamChunks?.provider[0]), 'data: {"content":"second"}');
|
|
});
|
|
|
|
test("createRequestLogger fallback match does not cross connectionId boundaries", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
usageHistory.clearPendingRequests();
|
|
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-a", true);
|
|
const secondId = usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-b", true);
|
|
|
|
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
|
|
enabled: true,
|
|
captureStreamChunks: true,
|
|
connectionId: "test-conn-b",
|
|
model: "gpt-4",
|
|
provider: "openai",
|
|
});
|
|
|
|
logger.appendProviderChunk('data: {"content":"conn-b"}');
|
|
|
|
const second = usageHistory.getPendingById().get(secondId);
|
|
assert.equal(stripChunkTs(second?.streamChunks?.provider[0]), 'data: {"content":"conn-b"}');
|
|
});
|
|
|
|
test("createRequestLogger without connectionId does not populate streamChunks", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
usageHistory.clearPendingRequests();
|
|
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-2", true, {
|
|
clientRequest: { messages: [{ role: "user", content: "hi" }] },
|
|
});
|
|
|
|
// create logger WITHOUT connectionId/model/provider — pushStreamChunks will bail
|
|
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
|
|
enabled: true,
|
|
captureStreamChunks: true,
|
|
// intentionally omit connectionId, model, provider
|
|
});
|
|
|
|
logger.appendProviderChunk('data: {"content":"hello"}');
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const detail = pending.details["test-conn-2"]?.["gpt-4 (openai)"]?.[0];
|
|
|
|
assert.ok(detail, "pending request detail should exist");
|
|
assert.equal(
|
|
detail.streamChunks,
|
|
undefined,
|
|
"streamChunks should be undefined when connectionId not provided to createRequestLogger"
|
|
);
|
|
});
|
|
|
|
test("createRequestLogger appendOpenAIChunk and appendConvertedChunk also populate streamChunks", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
usageHistory.clearPendingRequests();
|
|
|
|
usageHistory.trackPendingRequest("claude-3", "anthropic", "test-conn-3", true);
|
|
|
|
const logger = await createRequestLogger("anthropic", "openai", "claude-3", {
|
|
enabled: true,
|
|
captureStreamChunks: true,
|
|
connectionId: "test-conn-3",
|
|
model: "claude-3",
|
|
provider: "anthropic",
|
|
});
|
|
|
|
logger.appendOpenAIChunk('data: {"role":"assistant","content":"hi"}');
|
|
logger.appendConvertedChunk('data: {"content":"there"}');
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const detail = pending.details["test-conn-3"]?.["claude-3 (anthropic)"]?.[0];
|
|
|
|
assert.ok(detail?.streamChunks, "streamChunks should be set");
|
|
assert.equal(detail.streamChunks.openai.length, 1);
|
|
assert.equal(
|
|
stripChunkTs(detail.streamChunks.openai[0]),
|
|
'data: {"role":"assistant","content":"hi"}'
|
|
);
|
|
assert.equal(detail.streamChunks.client.length, 1);
|
|
assert.equal(stripChunkTs(detail.streamChunks.client[0]), 'data: {"content":"there"}');
|
|
});
|
|
|
|
test("createRequestLogger captures stream chunks even when enabled: false", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
usageHistory.clearPendingRequests();
|
|
|
|
usageHistory.trackPendingRequest("gpt-4", "openai", "test-conn-4", true);
|
|
|
|
// Logger is disabled (enabled: false) — previously this would return a
|
|
// no-op logger that didn't capture any chunks. Now stream chunks are
|
|
// always captured regardless of the enabled flag.
|
|
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
|
|
enabled: false,
|
|
captureStreamChunks: true,
|
|
connectionId: "test-conn-4",
|
|
model: "gpt-4",
|
|
provider: "openai",
|
|
});
|
|
|
|
logger.appendProviderChunk('data: {"content":"hello"}');
|
|
|
|
const pending = usageHistory.getPendingRequests();
|
|
const detail = pending.details["test-conn-4"]?.["gpt-4 (openai)"]?.[0];
|
|
|
|
assert.ok(detail?.streamChunks, "streamChunks should be set even when logger is disabled");
|
|
assert.equal(detail.streamChunks.provider.length, 1);
|
|
assert.equal(stripChunkTs(detail.streamChunks.provider[0]), 'data: {"content":"hello"}');
|
|
|
|
// But getPipelinePayloads should return null when disabled
|
|
assert.equal(
|
|
logger.getPipelinePayloads(),
|
|
null,
|
|
"pipeline payloads should be null when disabled"
|
|
);
|
|
});
|
|
|
|
test("createRequestLogger disabled logger other methods are no-ops", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
|
|
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
|
|
enabled: false,
|
|
captureStreamChunks: false,
|
|
});
|
|
|
|
// These should not throw
|
|
logger.logClientRawRequest("/endpoint", { foo: "bar" });
|
|
logger.logOpenAIRequest({ model: "gpt-4" });
|
|
logger.logTargetRequest("https://api.openai.com", {}, { model: "gpt-4" });
|
|
logger.logProviderResponse(200, "OK", {}, {});
|
|
logger.logConvertedResponse({ choices: [] });
|
|
logger.logError(new Error("test"));
|
|
logger.appendProviderChunk("test");
|
|
logger.appendOpenAIChunk("test");
|
|
logger.appendConvertedChunk("test");
|
|
|
|
assert.equal(logger.getPipelinePayloads(), null);
|
|
});
|
|
|
|
test("request logging never persists a raw hard-lease owner", async () => {
|
|
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
|
const rawOwner = `vlo_${"A".repeat(43)}`;
|
|
const logger = await createRequestLogger("openai", "openai", "gpt-4", {
|
|
enabled: true,
|
|
captureStreamChunks: false,
|
|
});
|
|
|
|
logger.logClientRawRequest(
|
|
"/v1/chat/completions",
|
|
{},
|
|
{
|
|
"X-OmniRoute-Lease-Owner": rawOwner,
|
|
"X-OmniRoute-Lease-Generation": "7",
|
|
}
|
|
);
|
|
|
|
const payload = logger.getPipelinePayloads()?.clientRawRequest;
|
|
assert.deepEqual(payload?.headers, {
|
|
"X-OmniRoute-Lease-Owner": "[REDACTED]",
|
|
"X-OmniRoute-Lease-Generation": "7",
|
|
});
|
|
assert.doesNotMatch(JSON.stringify(payload), new RegExp(rawOwner));
|
|
});
|
|
|
|
test("generic client snapshots exclude hard-lease control headers", async () => {
|
|
const { buildClientRawRequest } = await import("../../src/sse/handlers/chat/clientRawRequest.ts");
|
|
const request = new Request("http://x/v1/chat/completions", {
|
|
headers: {
|
|
"X-OmniRoute-Lease-Owner": `vlo_${"A".repeat(43)}`,
|
|
"X-OmniRoute-Lease-Generation": "7",
|
|
"X-Session-Id": "independent-routing-session",
|
|
},
|
|
});
|
|
const out = buildClientRawRequest(request, { model: "m" });
|
|
|
|
assert.equal(out.headers["x-omniroute-lease-owner"], undefined);
|
|
assert.equal(out.headers["x-omniroute-lease-generation"], undefined);
|
|
assert.equal(out.headers["x-session-id"], "independent-routing-session");
|
|
});
|