Files
OmniRoute/tests/unit/chatcore-reasoning-cache-write-guard.test.ts
Diego Rodrigues de Sa e Souza 93265eede3 test(infra): retry recursive temp-dir removal on main (main twin of #11968) (#12246)
* test(infra): retry recursive temp-dir removal on main (main twin of #11968)

`main` has been red since b342c1a361 on the vitest and integration gates:

  ✖ tests/unit/autoCombo/provider-family-combos.test.ts > auto/<family>
  ✖ chat pipeline applies Codex OAuth fingerprint and priority tier inside combos

Both call resetStorage() from beforeEach, which does an fs.rmSync(TEST_DATA_DIR,
{recursive: true, force: true}) with no retry, and intermittently loses the race
with a not-yet-released SQLite handle (ENOTEMPTY).

release/v3.8.51 fixed this in #11968 with a mechanical codemod adding
maxRetries/retryDelay to every recursive rm/rmSync/rmdirSync under tests/, but
that PR landed only on the release branch. Because main only receives work at
the release squash, it stayed broken for the whole cycle — and repo-wide gates
then turn every open PR into main red on checks unrelated to their diff.

This is the --base main twin: re-runs the same codemod that already shipped on
the release branch (scripts/ad-hoc/codemod-rm-maxretries.mjs), so the two
branches converge on identical test-teardown semantics. Test-only; no product
logic is touched.

The remaining three failures reported on #12133 (unit full suite exceeding its
4800s ceiling, package-artifact exceeding 1200s, and the boot-smoke that is
skipped as a consequence) are runner-contention timeouts, not code defects —
validate-release-green.mjs runs those heavy gates concurrently on one shared
hosted runner. There is no fix to port for those.

* chore(scripts): carry the rm-maxretries codemod onto main alongside its output

The codemod that generated the previous commit lives in the repo on
release/v3.8.51 (added by #11968) but was never on main. Bringing it over keeps
the tool next to the change it produced, so the transformation stays
reproducible and auditable from either branch.
2026-09-01 01:48:00 -03:00

201 lines
7.1 KiB
TypeScript

// Integration guard for the reasoning-cache write gate.
// The predicate is tested in isolation in chatCore-reasoning-cache-guard.test.ts; this
// file proves handleChatCore's ACTUAL call sites are wired to it, for both the
// non-streaming and streaming response paths, via the cache's own observable side
// effect (no spying on cacheReasoningFromAssistantMessage — same convention as
// tests/unit/chatcore-sanitization.test.ts and
// tests/unit/combo-context-overflow-compression-probe.test.ts: mock fetch, call the
// real handleChatCore, assert real behavior).
//
// Deepseek is also a replay provider (proven by the predicate test), but its wire
// format is openai-responses — a plain openai chat.completion mock would hit
// MALFORMED-200 and never reach the cache write. xiaomi-mimo serves the same
// predicate (REASONING_REPLAY_PROVIDERS member) while staying on the openai wire
// format, so both the non-streaming JSON mock and the streaming chat.completion.chunk
// SSE mock exercise the passthrough path with minimal translation noise.
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-chatcore-reasoning-cache-write-guard-")
);
process.env.DATA_DIR = TEST_DATA_DIR;
const { handleChatCore } = await import("../../open-sse/handlers/chatCore.ts");
const { lookupReasoning, clearReasoningCacheAll } =
await import("../../open-sse/services/reasoningCache.ts");
const core = await import("../../src/lib/db/core.ts");
function noopLog() {
return { debug() {}, info() {}, warn() {}, error() {} };
}
function nonStreamingUpstreamResponse(toolCallId: string, model: string) {
return new Response(
JSON.stringify({
id: "chatcmpl-reasoning-cache-guard",
object: "chat.completion",
model,
choices: [
{
index: 0,
message: {
role: "assistant",
content: null,
reasoning_content: "because the guard test says so",
tool_calls: [
{ id: toolCallId, type: "function", function: { name: "noop", arguments: "{}" } },
],
},
finish_reason: "tool_calls",
},
],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}),
{ status: 200, headers: { "content-type": "application/json" } }
);
}
function streamingUpstreamResponse(toolCallId: string) {
const firstChunk = {
id: "chatcmpl-reasoning-cache-stream-guard",
object: "chat.completion.chunk",
model: "probe",
choices: [
{
index: 0,
delta: {
role: "assistant",
reasoning_content: "because the guard test says so",
tool_calls: [
{
index: 0,
id: toolCallId,
type: "function",
function: { name: "noop", arguments: "{}" },
},
],
},
finish_reason: null,
},
],
};
const secondChunk = {
id: "chatcmpl-reasoning-cache-stream-guard",
object: "chat.completion.chunk",
model: "probe",
choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }],
};
const sseBody =
`data: ${JSON.stringify(firstChunk)}\n\n` +
`data: ${JSON.stringify(secondChunk)}\n\n` +
"data: [DONE]\n\n";
return new Response(sseBody, {
status: 200,
headers: { "content-type": "text/event-stream" },
});
}
async function invokeChatCoreNonStreaming(provider: string, model: string, toolCallId: string) {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => nonStreamingUpstreamResponse(toolCallId, model);
try {
const body = { model, messages: [{ role: "user", content: "call the tool" }], stream: false };
await handleChatCore({
body,
modelInfo: { provider, model, extendedContext: false },
credentials: { apiKey: "sk-test", providerSpecificData: {} },
log: noopLog(),
clientRawRequest: {
endpoint: "/v1/chat/completions",
body,
headers: new Headers({ accept: "application/json" }),
},
userAgent: "unit-test",
} as never);
} finally {
globalThis.fetch = originalFetch;
}
}
async function invokeChatCoreStreaming(provider: string, model: string, toolCallId: string) {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => streamingUpstreamResponse(toolCallId);
try {
const body = { model, messages: [{ role: "user", content: "call the tool" }], stream: true };
const result = await handleChatCore({
body,
modelInfo: { provider, model, extendedContext: false },
credentials: { apiKey: "sk-test", providerSpecificData: {} },
log: noopLog(),
clientRawRequest: {
endpoint: "/v1/chat/completions",
body,
headers: new Headers({ accept: "text/event-stream" }),
},
userAgent: "unit-test",
} as never);
// Drain the streaming response to trigger onStreamComplete (the cache write callback fires on flush/close)
if (result.success && result.response?.body) {
const reader = result.response.body.getReader();
const decoder = new TextDecoder();
let text = "";
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (value) text += decoder.decode(value as Uint8Array, { stream: true });
}
await new Promise((resolve) => setImmediate(resolve));
void text;
} else if (result.success) {
try {
await result.response.text();
await new Promise((resolve) => setImmediate(resolve));
} catch {}
}
} finally {
globalThis.fetch = originalFetch;
}
}
test.after(() => {
try {
core.resetDbInstance();
} catch {}
try {
clearReasoningCacheAll();
} catch {}
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
});
test("non-streaming: a replay provider (xiaomi-mimo) populates the reasoning cache", async () => {
const id = "tc-reasoning-cache-nonstream-mimo";
assert.equal(lookupReasoning(id), null);
await invokeChatCoreNonStreaming("xiaomi-mimo", "mimo-v1", id);
assert.equal(lookupReasoning(id), "because the guard test says so");
});
test("non-streaming: a non-replay provider (openai) does NOT populate the reasoning cache", async () => {
const id = "tc-reasoning-cache-nonstream-openai";
assert.equal(lookupReasoning(id), null);
await invokeChatCoreNonStreaming("openai", "gpt-5.1", id);
assert.equal(lookupReasoning(id), null);
});
test("streaming: a replay provider (xiaomi-mimo) populates the reasoning cache", async () => {
const id = "tc-reasoning-cache-stream-mimo";
assert.equal(lookupReasoning(id), null);
await invokeChatCoreStreaming("xiaomi-mimo", "mimo-v1", id);
assert.equal(lookupReasoning(id), "because the guard test says so");
});
test("streaming: a non-replay provider (openai) does NOT populate the reasoning cache", async () => {
const id = "tc-reasoning-cache-stream-openai";
assert.equal(lookupReasoning(id), null);
await invokeChatCoreStreaming("openai", "gpt-5.1", id);
assert.equal(lookupReasoning(id), null);
});