Files
OmniRoute/tests/unit/providers-constants-split.test.ts
Imam Wahyu Widodo d3331f8bca feat(providers): ClinePass OAuth login — dual-auth on top of #5942 (reconciles #5924) (#6126)
* feat(providers): rebase ClinePass dual-auth (OAuth + BYOK) onto release/v3.8.47

ClinePass now offers both sign-in methods on its dashboard page: OAuth
(reusing the Cline WorkOS flow, primary "Connect" button) or a pasted
BYOK API key ("Manual API key"), instead of only the API-key-only
provider shipped in #5942.

- Registry: authType oauth + oauth urls, alias aligned to "cp" (matches
  the OAUTH_PROVIDERS catalog alias so <alias>/<modelId> routing
  resolves); keeps the #6165 forceStream:true fix (streaming-only API).
- Executor: new buildClinepassHeaders() (src/shared/utils/clineAuth.ts)
  picks buildClineHeaders() for an OAuth accessToken or a plain Bearer +
  Cline identification headers for a BYOK key — extracted to a leaf
  module to avoid growing the frozen open-sse/executors/default.ts.
- Refresh: dispatch clinepass to the shared refreshClineToken() (was
  falling through to the generic refresh and failing silently).
- Catalog: admit the BYOK path through a dedicated
  DUAL_AUTH_APIKEY_PROVIDER_IDS gate (src/lib/providers/catalog.ts) so
  POST /api/providers accepts an apikey connection without flipping
  isOAuth off (which would break the primary Connect->OAuth routing).
- Dashboard: render both "Connect" + "Manual API key" buttons for
  clinepass (ConnectionsHeaderToolbar.tsx, EmptyConnectionsPlaceholder.tsx).
- Dedup: removed the now-redundant API-key-only APIKEY_PROVIDERS_GATEWAYS
  entry so ClinePass is listed once (OAuth-primary).
- oauth.ts: added the clinepass catalog entry (was reverted by staleness
  during rebase); src/lib/oauth/providers/index.ts: clinepass -> cline.

This branch was ~167 commits / weeks behind release/v3.8.47; a real
merge surfaced 61 conflicting files, several of which are already-shipped
fixes (forceStream #6165, zed-hosted, requesty, agentrouter CC-wire-image,
NVIDIA/Mistral/kimi executor fixes, chatCore hardening) that a naive
resolution would have silently reverted. Reconstructed clean on top of
current release/v3.8.47, isolating and re-applying only the clinepass
dual-auth feature and preserving every already-shipped fix untouched.

tokenRefresh.ts's frozen-file cap raised by the irreducible 1-line
`case "clinepass":` switch label (config/quality/file-size-baseline.json,
justified inline); open-sse/executors/default.ts stays under its cap via
the buildClinepassHeaders() extraction.

Regression guard: tests/unit/clinepass-provider.test.ts (15/15, extended
with the dual-auth admission-gate and alias-consistency guards).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* test(providers): update APIKEY_PROVIDERS spread-merge count 171->170

The ClinePass dual-auth rebase (this PR) removed the now-redundant
API-key-only APIKEY_PROVIDERS_GATEWAYS.clinepass entry (dedup — clinepass
is OAuth-primary now, with its BYOK path admitted through the
DUAL_AUTH_APIKEY_PROVIDER_IDS gate instead of a second catalog entry),
which drops the total APIKEY_PROVIDERS spread-merge count by one.
tests/unit/providers-constants-split.test.ts hardcoded the prior count
(171); updated to 170 to match, confirmed via CI (Unit Tests fast-path
1/2 and 2/2 both failed on the stale count).

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(oauth): register clinepass in PROVIDERS enum to fix Unknown provider error

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* chore(test): rebaseline oauth-providers-config.test.ts frozen size for clinepass entries

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(changelog): re-restore #6126 bullet after release sync

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: hajilok <hajilok@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-09 20:37:40 -03:00

92 lines
4.0 KiB
TypeScript

// Characterization of the providers.ts catalog split (god-file decomposition): the host became a
// barrel that re-exports 10 data catalogs now living under constants/providers/*, and APIKEY is
// merged from 6 semantic family files (apikey/<family>.ts). Locks: the public surface (every catalog
// + helpers still exported), the spread-merge integrity (166 APIKEY entries, no loss/dup), and that
// load-time Zod validation still runs. Pure-data move → behavior must be identical.
// Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc.,
// 171->167) plus #6126 (ClinePass dual-auth): the API-key-only APIKEY_PROVIDERS_GATEWAYS entry was
// removed as a duplicate now that clinepass is OAuth-primary (OAUTH_PROVIDERS.clinepass) with its
// BYOK path admitted through the DUAL_AUTH_APIKEY_PROVIDER_IDS gate instead (167->166).
import { test } from "node:test";
import assert from "node:assert/strict";
const P = await import("../../src/shared/constants/providers.ts");
test("barrel still exports every catalog + key helpers", () => {
for (const name of [
"NOAUTH_PROVIDERS",
"OAUTH_PROVIDERS",
"WEB_COOKIE_PROVIDERS",
"APIKEY_PROVIDERS",
"LOCAL_PROVIDERS",
"SEARCH_PROVIDERS",
"AUDIO_ONLY_PROVIDERS",
"UPSTREAM_PROXY_PROVIDERS",
"CLOUD_AGENT_PROVIDERS",
"SYSTEM_PROVIDERS",
"AI_PROVIDERS",
"ALIAS_TO_ID",
"ID_TO_ALIAS",
"getProviderById",
"getProviderByAlias",
"resolveProviderId",
]) {
assert.ok(name in P, `missing export: ${name}`);
}
});
test("APIKEY_PROVIDERS merges the 6 family files into 166 entries (no loss / no dup)", async () => {
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
assert.equal(keys.length, 166);
assert.equal(new Set(keys).size, 166, "duplicate keys after spread-merge");
// the merged object's entry-count equals the sum of the 6 semantic family files; families are a
// strict partition (every provider in exactly one), so the sum must be exactly 166.
const families: [string, string][] = [
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
["inference-hosts", "APIKEY_PROVIDERS_INFERENCE"],
["enterprise-cloud", "APIKEY_PROVIDERS_ENTERPRISE"],
["regional", "APIKEY_PROVIDERS_REGIONAL"],
["specialty-media", "APIKEY_PROVIDERS_SPECIALTY"],
];
let famTotal = 0;
const seen = new Set<string>();
for (const [file, exportName] of families) {
const mod = await import(`../../src/shared/constants/providers/apikey/${file}.ts`);
const famKeys = Object.keys(mod[exportName]);
famTotal += famKeys.length;
for (const k of famKeys) {
assert.ok(!seen.has(k), `provider ${k} appears in more than one family`);
seen.add(k);
}
}
assert.equal(famTotal, 166, "families must partition all 166 providers");
});
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {
const ai = (P as Record<string, Record<string, unknown>>).AI_PROVIDERS;
assert.ok(Object.keys(ai).length > 200);
assert.ok((P as Record<string, (id: string) => unknown>).getProviderById("openai"));
assert.ok((P as Record<string, (id: string) => unknown>).getProviderById("claude"));
// a moved catalog is reachable through the barrel re-export
assert.ok((P as Record<string, Record<string, unknown>>).APIKEY_PROVIDERS["openai"]);
});
test("each extracted data module is importable on its own", async () => {
const mods = [
["noauth", "NOAUTH_PROVIDERS"],
["oauth", "OAUTH_PROVIDERS"],
["web-cookie", "WEB_COOKIE_PROVIDERS"],
["local", "LOCAL_PROVIDERS"],
["search", "SEARCH_PROVIDERS"],
["audio", "AUDIO_ONLY_PROVIDERS"],
["upstream-proxy", "UPSTREAM_PROXY_PROVIDERS"],
["cloud-agent", "CLOUD_AGENT_PROVIDERS"],
["system", "SYSTEM_PROVIDERS"],
];
for (const [file, name] of mods) {
const m = await import(`../../src/shared/constants/providers/${file}.ts`);
assert.ok(m[name] && typeof m[name] === "object", `${file}.ts must export ${name}`);
}
});