Files
OmniRoute/tests/unit/mcp-stdio-json-purity.test.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

96 lines
4.3 KiB
TypeScript

// ENVIRONMENT NOTE (sandbox better-sqlite3 / glibc limitation, not a code defect):
// This test constructs or exercises a real better-sqlite3-backed SQLite database.
// better-sqlite3 is a native addon; production and CI load it normally, but some
// sandboxes/dev boxes ship a system glibc older than the prebuilt binary requires
// ("GLIBC_2.29 not found"), so the native module fails to dlopen and any test that
// reaches better-sqlite3 directly (or asserts stdout that the load-failure warning
// would pollute) fails HERE while passing in CI. This is a known environment
// limitation, not a defect in the code under test: the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 is unavailable. See
// tests/unit/_helpers/betterSqlite3Availability.ts for a guard helper.
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { join } from "node:path";
const ROOT = new URL("../..", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
/**
* Regression coverage: `omniroute --mcp` (the stdio transport Claude Desktop and other MCP
* clients spawn) must write nothing but JSON-RPC to stdout. DB init — a side effect of
* `createMcpServer()`'s tool registration reading compression settings — used to log via
* plain `console.log` before any redirect was in place (ES module static imports are hoisted
* and evaluate before any code inside the importing module's own functions runs, so a
* redirect placed inside server.ts itself was too late). That leaked lines like
* "[DB] Changing cache_size from 65536KB to 16384KB" straight onto stdout, corrupting the
* JSON-RPC stream client-side (e.g. Claude Desktop: `Unexpected token 'D', "[DB] Changi"...
* is not valid JSON`). Fixed by preloading bin/mcpStdioConsoleGuard.mjs via `node --import`
* (bin/mcp-server.mjs) — the only point early enough to run before the MCP entry's module
* graph evaluates at all.
*/
describe("omniroute --mcp stdio transport", () => {
it("writes only valid JSON-RPC to stdout — no DB init or other startup logging leaks through", async () => {
const child = spawn(process.execPath, [join(ROOT, "bin", "omniroute.mjs"), "--mcp"], {
cwd: ROOT,
env: process.env,
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
child.stdin.write(
`${JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: "2025-03-26",
capabilities: {},
clientInfo: { name: "regression-test", version: "0" },
},
})}\n`
);
// The full chain (omniroute.mjs CLI startup + spawned MCP child, each paying a tsx
// import + the child's DB init/migrations) takes ~10s on a warm dev box and longer on
// loaded CI runners — a fixed 4s sleep made this test red from birth. Poll for the
// first stdout line instead, then give the stream a short settle window so any
// late startup logging that WOULD corrupt the protocol still gets caught.
const deadline = Date.now() + 60_000;
while (!stdout.includes("\n") && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
await new Promise((resolve) => setTimeout(resolve, 1000));
child.kill();
const stdoutLines = stdout.split("\n").filter((line) => line.trim().length > 0);
assert.ok(
stdoutLines.length > 0,
"expected at least one line on stdout (the initialize response)"
);
for (const line of stdoutLines) {
assert.doesNotThrow(
() => JSON.parse(line),
`stdout line is not valid JSON (startup logging leaked onto stdout): ${line.slice(0, 120)}`
);
}
const initResponse = stdoutLines.map((line) => JSON.parse(line)).find((msg) => msg.id === 1);
assert.ok(initResponse, "expected an initialize response with id 1 on stdout");
assert.equal(initResponse.jsonrpc, "2.0");
// The DB init logging must still happen — just on stderr, not stdout.
assert.ok(
stderr.includes("[DB]"),
"expected DB init logging on stderr (proves it was redirected, not silently dropped)"
);
});
});