Files
OmniRoute/tests/unit/executor-default-anthropic-auth-8653.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

161 lines
6.5 KiB
TypeScript

/**
* Regression tests for #8653: Claude Code 2.1.220 returns 401 Missing API key
*
* Root cause: DefaultExecutor.buildHeaders for the built-in `claude`/`anthropic`
* providers emitted `Authorization: Bearer null` when the connection has an
* empty apiKey and no accessToken, and for `anthropic-compatible-*` nodes omitted
* the auth header entirely — both get forwarded to the upstream, producing the
* relayed "401 Missing API key" error.
*
* Fix: Guard against falsy credentials (no garbage headers), and extend the
* 9router b977bf74 dual-header fix (Bearer alongside x-api-key) to the built-in
* `claude`/`anthropic` providers for non-official baseUrls.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
// ── claude / anthropic — empty credentials guard ─────────────────────────
test("claude provider with empty apiKey and no accessToken does NOT emit Authorization header", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders(
{ apiKey: "", providerSpecificData: {} } as Record<string, unknown>,
true
) as Record<string, string>;
// Must not emit 'Bearer null' / 'Bearer undefined'
assert.equal(headers["Authorization"], undefined);
assert.equal(headers["x-api-key"], undefined);
});
test("anthropic provider with empty apiKey and no accessToken does NOT emit Authorization header", () => {
const executor = new DefaultExecutor("anthropic");
const headers = executor.buildHeaders(
{ apiKey: "", providerSpecificData: {} } as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["Authorization"], undefined);
assert.equal(headers["x-api-key"], undefined);
});
test("claude provider with both apiKey and accessToken as null/undefined does NOT emit Bearer null", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders(
{ providerSpecificData: {} } as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["Authorization"], undefined);
assert.equal(headers["x-api-key"], undefined);
});
// ── claude / anthropic — dual-header parity (9router b977bf74) ──────────
test("claude provider with non-official baseUrl sends BOTH x-api-key and Authorization: Bearer", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders(
{
apiKey: "k-third-party",
providerSpecificData: { baseUrl: "https://gateway.example/v1" },
} as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["x-api-key"], "k-third-party");
assert.equal(
headers["Authorization"],
"Bearer k-third-party",
"third-party claude upstream needs the Bearer fallback alongside x-api-key"
);
});
test("anthropic provider with non-official baseUrl sends BOTH x-api-key and Authorization: Bearer", () => {
const executor = new DefaultExecutor("anthropic");
const headers = executor.buildHeaders(
{
apiKey: "k-third-party",
providerSpecificData: { baseUrl: "https://anthropic-proxy.example/v1" },
} as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["x-api-key"], "k-third-party");
assert.equal(
headers["Authorization"],
"Bearer k-third-party",
"third-party anthropic upstream needs the Bearer fallback alongside x-api-key"
);
});
// ── claude / anthropic — official api.anthropic.com stays x-api-key-only ─
test("claude provider with official api.anthropic.com baseUrl: x-api-key only, no Bearer", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders(
{
apiKey: "k-official",
providerSpecificData: { baseUrl: "https://api.anthropic.com/v1" },
} as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["x-api-key"], "k-official");
assert.equal(
headers["Authorization"],
undefined,
"official api.anthropic.com must NOT receive a Bearer header alongside x-api-key"
);
});
test("anthropic provider with official api.anthropic.com baseUrl: x-api-key only, no Bearer", () => {
const executor = new DefaultExecutor("anthropic");
const headers = executor.buildHeaders(
{
apiKey: "k-official",
providerSpecificData: { baseUrl: "https://api.anthropic.com/v1" },
} as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["x-api-key"], "k-official");
assert.equal(headers["Authorization"], undefined);
});
test("claude provider with empty baseUrl (defaults to official): x-api-key only, no Bearer", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders(
{ apiKey: "k-empty", providerSpecificData: {} } as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["x-api-key"], "k-empty");
assert.equal(headers["Authorization"], undefined);
});
// ── claude OAuth (accessToken-only) keeps Authorization: Bearer ──────────
test("claude provider with accessToken-only (OAuth mode): Authorization Bearer, no x-api-key", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders(
{ accessToken: "oauth-token", providerSpecificData: {} } as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["Authorization"], "Bearer oauth-token");
assert.equal(headers["x-api-key"], undefined);
});
test("anthropic provider with accessToken-only (OAuth mode): Authorization Bearer, no x-api-key", () => {
const executor = new DefaultExecutor("anthropic");
const headers = executor.buildHeaders(
{ accessToken: "oauth-token", providerSpecificData: {} } as Record<string, unknown>,
true
) as Record<string, string>;
assert.equal(headers["Authorization"], "Bearer oauth-token");
assert.equal(headers["x-api-key"], undefined);
});
// ── existing behavior preserved ─────────────────────────────────────────
test("claude provider with apiKey on default baseUrl: x-api-key only, respects existing behavior", () => {
const executor = new DefaultExecutor("claude");
const headers = executor.buildHeaders({ apiKey: "claude-key" } as Record<string, unknown>, true) as Record<string, string>;
assert.equal(headers["x-api-key"], "claude-key");
assert.equal(headers["Authorization"], undefined);
});