Files
OmniRoute/tests/unit/mcp-web-search-provider-enum-contract.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

74 lines
3.4 KiB
TypeScript

// #10209 — contract test: the MCP `omniroute_web_search` `provider` enum is now
// generated dynamically from the search registry (`getActiveSearchProviders`).
// This pins the invariant that the dynamic enum does not silently break the Zod
// schema exposed to MCP clients (or the tool's scope), regardless of future
// registry additions/removals or `disabled` flags. If the registry drifts, this
// test turns red before any client sees a broken/widened contract.
import test from "node:test";
import assert from "node:assert/strict";
const { getActiveSearchProviders } = await import("../../open-sse/mcp-server/schemas/providerEnums.ts");
const { SEARCH_PROVIDERS } = await import("../../open-sse/config/searchRegistry.ts");
const { webSearchInput, webSearchTool } = await import("../../open-sse/mcp-server/schemas/tools.ts");
// The provider set the tool historically exposed to MCP clients (the hardcoded
// enum this PR replaced). Every one of these MUST keep parsing so existing
// clients are never broken by the dynamic enum.
const LEGACY_CONTRACT_PROVIDERS = [
"serper-search",
"brave-search",
"perplexity-search",
"exa-search",
"tavily-search",
"google-pse-search",
"linkup-search",
"searchapi-search",
"searxng-search",
] as const;
function activeRegistryIds(): string[] {
return Object.values(SEARCH_PROVIDERS)
.filter((p) => !p.disabled)
.map((p) => p.id)
.sort();
}
test("getActiveSearchProviders() is a non-empty tuple of active registry providers", () => {
const active = getActiveSearchProviders();
assert.ok(Array.isArray(active), "should return an array (Zod enum tuple)");
assert.ok(active.length > 0, "dynamic enum must never be empty");
assert.deepEqual([...active].sort(), activeRegistryIds());
});
test("web_search inputSchema provider enum equals the active provider set", () => {
const shape = webSearchInput.shape as { provider: { unwrap: () => unknown } };
const providerManager = shape.provider; // ZodOptional around the ZodEnum
const unwrapped = providerManager.unwrap() as { options: string[] };
const options = unwrapped.options;
assert.ok(Array.isArray(options), "provider field should be an enum");
assert.ok(options.length > 0, "provider enum should have at least one value");
assert.deepEqual([...options].sort(), activeRegistryIds());
});
test("dynamic enum is a superset of the legacy contract (existing clients not broken)", () => {
for (const id of LEGACY_CONTRACT_PROVIDERS) {
const result = webSearchInput.safeParse({ query: "test", provider: id });
assert.ok(result.success, `legacy provider "${id}" must still parse`);
}
// Every active registry provider must also be selectable by an MCP client.
for (const id of activeRegistryIds()) {
const result = webSearchInput.safeParse({ query: "test", provider: id });
assert.ok(result.success, `active registry provider "${id}" must parse`);
}
});
test("unknown provider values are rejected (contract stays tight)", () => {
const result = webSearchInput.safeParse({ query: "test", provider: "no-such-search-provider" });
assert.equal(result.success, false);
});
test("tool registration + scope are unaffected by the dynamic enum", () => {
assert.equal(webSearchTool.name, "omniroute_web_search");
assert.equal(webSearchTool.inputSchema, webSearchInput);
assert.ok(webSearchTool.scopes.includes("execute:search"), "web search tool scope must be retained");
});