/** * T-07 config-hook backward-compat shim tests. * * Covers `createOmniRouteConfigHook(opts, deps)`: * - happy path: valid auth.json → mutates input.provider[id] with the * stripped per-model shape (mirroring `@omniroute/opencode-provider`). * - no-op paths: missing auth.json, malformed JSON, missing apiKey, * missing baseURL, existing input.provider[id] (manual override). * - fail-open: /v1/models error → stub `models: {}`; /api/combos error → * models-only static catalog. * - baseURL resolution: opts.baseURL → auth.json.baseURL fallback. * - multi-instance: two plugins with different providerIds publish to * their own keys without collision. * - cache sharing: provider hook + config hook on the same Map dedupe * fetcher invocations. * - sibling-shape parity: emitted entries carry only * `{name, attachment?, reasoning?, temperature?, tool_call?, limit?}` * — never the rich ModelV2 nested capabilities tree. * * Mocking strategy mirrors `provider.test.ts` and `combos.test.ts`: every * dependency (`readAuthJson`, `fetcher`, `combosFetcher`, `now`, `cache`, * `logger`) is dependency-injected at hook construction. No global * `fs/promises` or `fetch` monkey-patch needed. */ import test from "node:test"; import assert from "node:assert/strict"; import type { Config } from "@opencode-ai/plugin"; import { buildStaticProviderEntry, createOmniRouteConfigHook, createOmniRouteProviderHook, OmniRoutePlugin, resolveOmniRoutePluginOptions, type OmniRouteCombosFetcher, type OmniRouteFetchCache, type OmniRouteModelsFetcher, type OmniRouteRawCombo, type OmniRouteRawModelEntry, type OmniRouteReadAuthJson, type OmniRouteStaticProviderEntry, } from "../src/index.js"; // ──────────────────────────────────────────────────────────────────────────── // Fixtures // ──────────────────────────────────────────────────────────────────────────── const MODEL_CLAUDE: OmniRouteRawModelEntry = { id: "claude-sonnet-4-6", capabilities: { tool_calling: true, reasoning: true, vision: true, thinking: false, temperature: true, }, context_length: 200_000, max_output_tokens: 64_000, max_input_tokens: 180_000, input_modalities: ["text", "image"], output_modalities: ["text"], }; const MODEL_GEMINI: OmniRouteRawModelEntry = { id: "gemini-3-flash", capabilities: { tool_calling: true, reasoning: false, vision: true, thinking: false }, context_length: 1_000_000, max_output_tokens: 8_192, input_modalities: ["text", "image"], output_modalities: ["text"], }; const COMBO_CLAUDE_TIER: OmniRouteRawCombo = { id: "combo-claude-tier", name: "Claude Tier", models: [ { id: "s1", kind: "model", model: "claude-sonnet-4-6", weight: 100 }, { id: "s2", kind: "model", model: "gemini-3-flash", weight: 50 }, ], }; // ──────────────────────────────────────────────────────────────────────────── // Helpers (DI stubs — mirrors patterns in provider.test.ts / combos.test.ts) // ──────────────────────────────────────────────────────────────────────────── function stubReadAuthJson( value: Record | undefined | null ): OmniRouteReadAuthJson & { callCount: () => number } { let n = 0; const f: OmniRouteReadAuthJson = async () => { n++; return value as never; }; return Object.assign(f, { callCount: () => n }); } function throwingReadAuthJson(): OmniRouteReadAuthJson & { callCount: () => number } { let n = 0; const f: OmniRouteReadAuthJson = async () => { n++; throw new Error("EACCES"); }; return Object.assign(f, { callCount: () => n }); } function stubModelsFetcher( payload: OmniRouteRawModelEntry[] ): OmniRouteModelsFetcher & { callCount: () => number; callsBy: () => Array<[string, string]> } { let n = 0; const calls: Array<[string, string]> = []; const f: OmniRouteModelsFetcher = async (baseURL, apiKey) => { n++; calls.push([baseURL, apiKey]); return payload; }; return Object.assign(f, { callCount: () => n, callsBy: () => calls }); } function stubCombosFetcher( payload: OmniRouteRawCombo[] ): OmniRouteCombosFetcher & { callCount: () => number; callsBy: () => Array<[string, string]> } { let n = 0; const calls: Array<[string, string]> = []; const f: OmniRouteCombosFetcher = async (baseURL, apiKey) => { n++; calls.push([baseURL, apiKey]); return payload; }; return Object.assign(f, { callCount: () => n, callsBy: () => calls }); } function throwingModelsFetcher(): OmniRouteModelsFetcher & { callCount: () => number } { let n = 0; const f: OmniRouteModelsFetcher = async () => { n++; throw new Error("ECONNREFUSED"); }; return Object.assign(f, { callCount: () => n }); } function throwingCombosFetcher(): OmniRouteCombosFetcher & { callCount: () => number } { let n = 0; const f: OmniRouteCombosFetcher = async () => { n++; throw new Error("403 Forbidden"); }; return Object.assign(f, { callCount: () => n }); } interface WarnCapture { warn: (...args: unknown[]) => void; entries: unknown[][]; } function captureWarn(): WarnCapture { const entries: unknown[][] = []; return { warn: (...args: unknown[]) => { entries.push(args); }, entries, }; } function makeInput(initialProvider: Record = {}): Config { // Config = Omit & {plugin?: ...}. We only touch the // `provider` slot, so a partial cast is acceptable for these tests. return { provider: initialProvider } as unknown as Config; } // ──────────────────────────────────────────────────────────────────────────── // 1. Happy path — valid auth.json + apiKey + baseURL → mutates input.provider // ──────────────────────────────────────────────────────────────────────────── test("config: with valid auth.json + apiKey + baseURL → mutates input.provider[id] with stripped models block", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-test-1", baseURL: "https://or.example.com/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]); const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); const provider = (input as { provider: Record }).provider; const entry = provider.omniroute; assert.ok(entry, "input.provider.omniroute set"); assert.equal(entry.npm, "@ai-sdk/openai-compatible"); assert.equal(entry.name, "OmniRoute"); assert.equal(entry.options.baseURL, "https://or.example.com/v1"); assert.equal(entry.options.apiKey, "sk-test-1"); // Stripped per-model shape: name + cap flags only, NO nested // capabilities.input.* tree, NO cost block. const claude = entry.models["claude-sonnet-4-6"]; assert.ok(claude, "claude model surfaced"); assert.equal(claude.name, "claude-sonnet-4-6"); assert.equal(claude.attachment, true); assert.equal(claude.reasoning, true); assert.equal(claude.temperature, true); assert.equal(claude.tool_call, true); assert.equal(claude.limit?.context, 200_000); assert.equal(claude.limit?.input, 180_000); assert.equal(claude.limit?.output, 64_000); // Combo present + LCD'd (gemini's reasoning=false → combo reasoning=false). const combo = entry.models["combo-claude-tier"]; assert.ok(combo, "combo surfaced"); assert.equal(combo.name, "Claude Tier"); assert.equal(combo.reasoning, false, "LCD: any member reasoning=false → combo reasoning=false"); assert.equal(combo.tool_call, true); assert.equal(combo.limit?.context, 200_000, "LCD: min(200_000, 1_000_000)"); }); // ──────────────────────────────────────────────────────────────────────────── // 2. Missing auth.json → no-op, no throw, no mutation // ──────────────────────────────────────────────────────────────────────────── test("config: missing auth.json file → no-op, no throw, no input mutation", async () => { const readAuthJson = stubReadAuthJson(undefined); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); assert.deepEqual((input as { provider: Record }).provider, {}); assert.equal(fetcher.callCount(), 0, "no fetch on missing auth.json"); assert.equal(combosFetcher.callCount(), 0, "no combos fetch on missing auth.json"); // One breadcrumb — the missing-apiKey path. assert.ok( logger.entries.some((e) => String(e[0]).includes("no apiKey")), "breadcrumb emitted" ); }); // ──────────────────────────────────────────────────────────────────────────── // 3. Malformed auth.json → no-op + warn once // ──────────────────────────────────────────────────────────────────────────── test("config: malformed auth.json → no-op + warn once", async () => { // stubReadAuthJson returns `null` to signal malformed JSON (matches // defaultReadAuthJson's contract). const readAuthJson = stubReadAuthJson(null); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); assert.deepEqual((input as { provider: Record }).provider, {}); assert.equal(fetcher.callCount(), 0); // First warn = "failed to parse"; second warn = "no apiKey". assert.ok( logger.entries.some((e) => String(e[0]).includes("failed to parse")), "parse-failure breadcrumb emitted" ); }); // ──────────────────────────────────────────────────────────────────────────── // 4. Existing input.provider[id] → no overwrite (respect manual override) // ──────────────────────────────────────────────────────────────────────────── test("config: existing input.provider[id] → no overwrite (respect manual override)", async () => { const manual = { npm: "@ai-sdk/openai-compatible", name: "Manual OmniRoute", options: { baseURL: "http://manual/v1", apiKey: "manual-key" }, models: { "manual-model": { name: "manual-model" } }, }; const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput({ omniroute: manual }); await hook(input); const provider = (input as { provider: Record }).provider; assert.equal(provider.omniroute, manual, "manual override preserved by reference"); assert.equal(fetcher.callCount(), 0, "no fetch — short-circuited before I/O"); assert.equal(readAuthJson.callCount(), 0, "no auth.json read either"); assert.ok( logger.entries.some((e) => String(e[0]).includes("already set")), "override breadcrumb emitted" ); }); // ──────────────────────────────────────────────────────────────────────────── // 5. fetchers throw → warn + emit stub entry with `models: {}` // ──────────────────────────────────────────────────────────────────────────── test("config: fetchers throw → warn + emit stub entry with models: {}", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, }); const fetcher = throwingModelsFetcher(); const combosFetcher = throwingCombosFetcher(); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); const entry = (input as { provider: Record }).provider .omniroute; assert.ok(entry, "stub provider entry published even when fetchers fail"); assert.equal(entry.npm, "@ai-sdk/openai-compatible"); assert.deepEqual(entry.models, {}, "models stub is empty object"); assert.equal(entry.options.baseURL, "https://or.example/v1"); assert.equal(entry.options.apiKey, "sk-test"); // Both warns fired. assert.ok( logger.entries.some((e) => String(e[0]).includes("/v1/models fetch failed")), "models-fetch breadcrumb emitted" ); assert.ok( logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")), "combos-fetch breadcrumb emitted" ); }); // ──────────────────────────────────────────────────────────────────────────── // 6. Combos fetcher throws → models-only catalog (no combos in models block) // ──────────────────────────────────────────────────────────────────────────── test("config: combos fetcher throws → emit models-only catalog (no combos in models block)", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]); const combosFetcher = throwingCombosFetcher(); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); const entry = (input as { provider: Record }).provider .omniroute; assert.ok(entry); const ids = Object.keys(entry.models).sort(); assert.deepEqual(ids, ["claude-sonnet-4-6", "gemini-3-flash"]); assert.equal(entry.models["combo-claude-tier"], undefined, "no combo entry"); assert.ok( logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")), "combos-fetch breadcrumb emitted" ); }); // ──────────────────────────────────────────────────────────────────────────── // 7. baseURL from auth.json takes precedence when opts.baseURL absent // ──────────────────────────────────────────────────────────────────────────── test("config: baseURL from auth.json takes precedence when opts.baseURL absent", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-test", baseURL: "https://creds.example/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, // NO opts.baseURL { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); assert.equal(fetcher.callsBy()[0][0], "https://creds.example/v1"); const entry = (input as { provider: Record }).provider .omniroute; assert.equal(entry.options.baseURL, "https://creds.example/v1"); }); test("config: opts.baseURL wins over auth.json's stored baseURL", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-test", baseURL: "https://creds.example/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute", baseURL: "https://opts.example/v1" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); assert.equal(fetcher.callsBy()[0][0], "https://opts.example/v1"); const entry = (input as { provider: Record }).provider .omniroute; assert.equal(entry.options.baseURL, "https://opts.example/v1"); }); test("config: no baseURL resolvable (no opts, no auth.json baseURL) → no-op", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-test" }, // NO baseURL on the credential }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, // NO opts.baseURL { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); assert.deepEqual((input as { provider: Record }).provider, {}); assert.equal(fetcher.callCount(), 0); assert.ok( logger.entries.some((e) => String(e[0]).includes("no baseURL")), "no-baseURL breadcrumb emitted" ); }); // ──────────────────────────────────────────────────────────────────────────── // 8. Multi-instance: two plugins with different providerIds publish to // their own keys without collision. // ──────────────────────────────────────────────────────────────────────────── test("config: multi-instance — two plugins with different providerIds publish to their own keys without collision", async () => { const readAuthJson = stubReadAuthJson({ "omniroute-prod": { type: "api", key: "sk-prod", baseURL: "https://prod.example/v1", }, "omniroute-preprod": { type: "api", key: "sk-preprod", baseURL: "https://preprod.example/v1", }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hookA = createOmniRouteConfigHook( { providerId: "omniroute-prod" }, { readAuthJson, fetcher, combosFetcher, logger } ); const hookB = createOmniRouteConfigHook( { providerId: "omniroute-preprod" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hookA(input); await hookB(input); const provider = (input as { provider: Record }).provider; assert.ok(provider["omniroute-prod"], "prod block present"); assert.ok(provider["omniroute-preprod"], "preprod block present"); assert.equal(provider["omniroute-prod"].options.apiKey, "sk-prod"); assert.equal(provider["omniroute-preprod"].options.apiKey, "sk-preprod"); assert.equal(provider["omniroute-prod"].options.baseURL, "https://prod.example/v1"); assert.equal(provider["omniroute-preprod"].options.baseURL, "https://preprod.example/v1"); assert.notEqual( provider["omniroute-prod"], provider["omniroute-preprod"], "blocks are distinct references" ); }); // ──────────────────────────────────────────────────────────────────────────── // 9. Cache sharing: provider hook + config hook on the same Map dedupe // fetcher invocations. // ──────────────────────────────────────────────────────────────────────────── test("config + provider share cache: second call uses cached fetch result (single fetch per TTL)", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-shared", baseURL: "https://or.example/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]); const sharedCache: OmniRouteFetchCache = new Map(); const logger = captureWarn(); const configHook = createOmniRouteConfigHook( { providerId: "omniroute", baseURL: "https://or.example/v1", modelCacheTtl: 60_000 }, { readAuthJson, fetcher, combosFetcher, cache: sharedCache, logger } ); const providerHook = createOmniRouteProviderHook( { providerId: "omniroute", baseURL: "https://or.example/v1", modelCacheTtl: 60_000 }, { fetcher, combosFetcher, cache: sharedCache } ); // Simulate OC ≥1.14.49 cold start: config fires first, populates cache, // then provider.models() reuses the cached raw results. const input = makeInput(); await configHook(input); assert.equal(fetcher.callCount(), 1, "config fired the only models fetch"); assert.equal(combosFetcher.callCount(), 1, "config fired the only combos fetch"); // provider hook then runs — should hit the shared cache, NOT refetch. const apiAuth = { type: "api", key: "sk-shared" }; await providerHook.models!({} as never, { auth: apiAuth as never }); assert.equal(fetcher.callCount(), 1, "provider reused cached models"); assert.equal(combosFetcher.callCount(), 1, "provider reused cached combos"); }); test("provider → config order also dedupes (cache populated by provider, consumed by config)", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk-reverse", baseURL: "https://or.example/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const sharedCache: OmniRouteFetchCache = new Map(); const logger = captureWarn(); const configHook = createOmniRouteConfigHook( { providerId: "omniroute", baseURL: "https://or.example/v1", modelCacheTtl: 60_000 }, { readAuthJson, fetcher, combosFetcher, cache: sharedCache, logger } ); const providerHook = createOmniRouteProviderHook( { providerId: "omniroute", baseURL: "https://or.example/v1", modelCacheTtl: 60_000 }, { fetcher, combosFetcher, cache: sharedCache } ); await providerHook.models!({} as never, { auth: { type: "api", key: "sk-reverse" } as never, }); assert.equal(fetcher.callCount(), 1); const input = makeInput(); await configHook(input); assert.equal(fetcher.callCount(), 1, "config reused cached models"); }); // ──────────────────────────────────────────────────────────────────────────── // 10. Stripped models shape matches sibling provider spec // (`{name, attachment, reasoning, tool_call, temperature, limit?}`). // ──────────────────────────────────────────────────────────────────────────── test("buildStaticProviderEntry: stripped per-model shape matches sibling @omniroute/opencode-provider", () => { const resolved = resolveOmniRoutePluginOptions({ providerId: "omniroute", displayName: "OmniRoute", }); const block = buildStaticProviderEntry( [MODEL_CLAUDE, MODEL_GEMINI], [], resolved, "https://or.example/v1", "sk-test" ); // Top-level provider entry shape — ONLY these four keys. assert.deepEqual(Object.keys(block).sort(), ["models", "name", "npm", "options"]); assert.equal(block.npm, "@ai-sdk/openai-compatible"); assert.equal(block.name, "OmniRoute"); assert.deepEqual(Object.keys(block.options).sort(), ["apiKey", "baseURL"]); // Per-model entry shape — STRIPPED (no nested capabilities tree, no // cost block, no providerID/api/status/headers/release_date that // ModelV2 carries). Allowed keys: name, attachment, reasoning, // temperature, tool_call, limit. const allowedKeys = new Set([ "name", "attachment", "reasoning", "temperature", "tool_call", "limit", ]); for (const [id, entry] of Object.entries(block.models)) { for (const key of Object.keys(entry)) { assert.ok(allowedKeys.has(key), `${id}.${key} is not in the stripped sibling shape`); } // capabilities (ModelV2-only) must NOT leak. assert.equal( (entry as Record).capabilities, undefined, `${id} must not carry nested capabilities tree` ); // cost (ModelV2-only) must NOT leak. assert.equal( (entry as Record).cost, undefined, `${id} must not carry cost block` ); } // Sanity: claude entry has all expected stripped fields. const claude = block.models["claude-sonnet-4-6"]; assert.equal(typeof claude.name, "string"); assert.equal(typeof claude.attachment, "boolean"); assert.equal(typeof claude.reasoning, "boolean"); assert.equal(typeof claude.temperature, "boolean"); assert.equal(typeof claude.tool_call, "boolean"); assert.equal(typeof claude.limit?.context, "number"); }); test("buildStaticProviderEntry: empty fetch results → stub block with models: {}", () => { const resolved = resolveOmniRoutePluginOptions({ providerId: "omniroute" }); const block = buildStaticProviderEntry([], [], resolved, "https://or.example/v1", "sk-test"); assert.deepEqual(block.models, {}); assert.equal(block.options.apiKey, "sk-test"); }); test("buildStaticProviderEntry: hidden combos are excluded", () => { const resolved = resolveOmniRoutePluginOptions({ providerId: "omniroute" }); const block = buildStaticProviderEntry( [MODEL_CLAUDE], [{ ...COMBO_CLAUDE_TIER, isHidden: true }], resolved, "https://or.example/v1", "sk-test" ); assert.equal(block.models["combo-claude-tier"], undefined); assert.ok(block.models["claude-sonnet-4-6"]); }); // ──────────────────────────────────────────────────────────────────────────── // Integration: OmniRoutePlugin factory now exposes config hook // ──────────────────────────────────────────────────────────────────────────── test("OmniRoutePlugin factory exposes config hook alongside auth + provider", async () => { const hooks = await OmniRoutePlugin({} as never, { providerId: "omniroute" }); assert.equal(typeof hooks.config, "function", "config hook present"); assert.ok(hooks.auth, "auth hook present"); assert.ok(hooks.provider, "provider hook present"); }); // ──────────────────────────────────────────────────────────────────────────── // Edge cases / robustness // ──────────────────────────────────────────────────────────────────────────── test("config: auth.json entry of wrong type (oauth) → no-op", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "oauth", refresh: "r", access: "a", expires: 0 }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute", baseURL: "https://or.example/v1" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); assert.deepEqual((input as { provider: Record }).provider, {}); assert.equal(fetcher.callCount(), 0); }); test("config: readAuthJson throws → treat as missing file (silent fallback)", async () => { const readAuthJson = throwingReadAuthJson(); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute", baseURL: "https://or.example/v1" }, { readAuthJson, fetcher, combosFetcher, logger } ); const input = makeInput(); await hook(input); assert.deepEqual((input as { provider: Record }).provider, {}); assert.equal(readAuthJson.callCount(), 1); assert.equal(fetcher.callCount(), 0); }); test("config: initialises input.provider when undefined", async () => { const readAuthJson = stubReadAuthJson({ omniroute: { type: "api", key: "sk", baseURL: "https://or.example/v1" }, }); const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const combosFetcher = stubCombosFetcher([]); const logger = captureWarn(); const hook = createOmniRouteConfigHook( { providerId: "omniroute" }, { readAuthJson, fetcher, combosFetcher, logger } ); // input with NO provider field at all const input = {} as Config; await hook(input); const provider = (input as { provider?: Record }).provider; assert.ok(provider, "provider bag initialised"); assert.ok(provider!.omniroute); });