Files
OmniRoute/tests/unit/9474-claude-code-oauth-mismap.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

110 lines
5.0 KiB
TypeScript

// Repro/regression test for issue #9474
// Claude Code OAuth device flow (`omniroute oauth start --provider claude-code`)
// failed with 401 because the CLI mapped `claude-code` to the unrelated
// `command-code` (CommandCode.ai) API-key provider instead of the real
// Anthropic `claude` browser-PKCE OAuth flow.
//
// This test asserts the FIXED behavior:
// - `claude-code` is labeled `flow: "browser"` (not `"device"`)
// - `runDeviceFlow` no longer remaps `claude-code` to `command-code`
// - the CLI resolves the user-facing `claude-code` id to the backend
// OAuth provider key `claude` and calls the existing browser-PKCE
// actions (`authorize` / `exchange`), never `command-code`.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, "..", "..");
const oauthCliPath = join(repoRoot, "bin/cli/commands/oauth.mjs");
const oauthCli = readFileSync(oauthCliPath, "utf8");
test("#9474: claude-code is advertised as a browser flow (not device)", () => {
// The user-facing entry must be flow: "browser" — Anthropic Claude OAuth is
// authorization_code_pkce (browser), not a device-code flow.
assert.match(
oauthCli,
/\{\s*id:\s*"claude-code",\s*name:\s*"Claude Code \(OAuth\)",\s*flow:\s*"browser"\s*\}/,
'claude-code must be labeled flow: "browser" (Anthropic uses a browser PKCE flow)'
);
// And it must NOT be labeled device.
assert.doesNotMatch(
oauthCli,
/\{\s*id:\s*"claude-code",\s*name:\s*"Claude Code \(OAuth\)",\s*flow:\s*"device"\s*\}/,
'claude-code must not be labeled flow: "device"'
);
});
test("#9474: runDeviceFlow no longer remaps claude-code -> command-code", () => {
// The mismap line must be gone entirely.
assert.doesNotMatch(
oauthCli,
/claude-code"\s*\?\s*"command-code"/,
"the claude-code -> command-code remap in runDeviceFlow must be removed"
);
// And runDeviceFlow must not call the command-code provider route via apiFetch.
// (Comments explaining the historical bug may mention the path; only an actual
// apiFetch call to it is a regression.)
assert.doesNotMatch(
oauthCli,
/apiFetch\(\s*`\/api\/providers\/command-code\/auth\/start/,
"runDeviceFlow must not apiFetch /api/providers/command-code/auth/start"
);
});
test("#9474: CLI resolves user-facing claude-code to backend key claude", () => {
// The CLI must map the user-facing id `claude-code` to the backend OAuth
// provider key `claude` (the key /api/oauth/[provider]/... expects).
// Look for a resolution helper that produces "claude" for "claude-code".
assert.match(
oauthCli,
/claude-code"\s*,?\s*.*?"claude"/,
"claude-code must resolve to backend OAuth key claude"
);
});
test("#9474: browser flow for claude-code targets /api/oauth/claude/authorize (not command-code, not a non-existent /start)", () => {
// The fixed browser flow must call the existing server action `authorize`
// on the resolved backend key `claude` — not the non-existent `/start`
// action, and not the command-code provider route.
// The runBrowserFlow helper must use the resolved backend key, not def.id,
// so claude-code routes to /api/oauth/claude/... .
assert.match(
oauthCli,
/\/api\/oauth\/\$\{[^}]*backendKey[^}]*\}\/authorize/,
"runBrowserFlow must call /api/oauth/${backendKey}/authorize using the resolved backend key"
);
assert.match(
oauthCli,
/\/api\/oauth\/\$\{[^}]*backendKey[^}]*\}\/exchange/,
"runBrowserFlow must call /api/oauth/${backendKey}/exchange using the resolved backend key"
);
// The old broken non-existent `/start` action must be gone from runBrowserFlow.
// Comments explaining the historical bug may mention the path; only an actual
// apiFetch call to it is a regression.
assert.doesNotMatch(
oauthCli,
/apiFetch\(\s*`\/api\/oauth\/\$\{def\.id\}\/start/,
"runBrowserFlow must not apiFetch the non-existent /api/oauth/${def.id}/start action"
);
});
test("#9474: real Anthropic Claude OAuth is provider `claude` with browser PKCE flow (not device)", async () => {
const mod = await import("../../src/lib/oauth/providers/claude.ts");
const claude = mod.claude;
assert.equal(claude.flowType, "authorization_code_pkce");
assert.notEqual(claude.flowType, "device_code");
assert.equal(claude.config.authorizeUrl, "https://claude.ai/oauth/authorize");
});
test("#9474: command-code provider is the unrelated CommandCode.ai apikey provider (unchanged, sanity)", async () => {
const mod = await import("../../open-sse/config/providers/registry/command-code/index.ts");
const commandCodeProvider = mod.command_codeProvider;
assert.equal(commandCodeProvider.id, "command-code");
assert.equal(commandCodeProvider.baseUrl, "https://api.commandcode.ai");
// command-code must remain distinct from the Anthropic claude provider.
assert.notEqual(commandCodeProvider.id, "claude");
});