Files
OmniRoute/tests/unit/ts7-executor-override-signatures.test.ts
backryun 06326a3c80 refactor(sse): stop three executors shadowing BaseExecutor.buildHeaders (#8498)
`BaseExecutor.buildHeaders(credentials, stream?, clientHeaders?, model?, health?)` was
shadowed in three executors by same-named helpers with unrelated signatures:

  hailuo-web  private   buildHeaders(token: string, yy: string)
  lmarena     protected buildHeaders(_model: string, credentials: unknown, _body: unknown)
  qwen-web    private   buildHeaders(token: string, cookieHeader: string, chatId?: string)

Name collisions, not overrides — each reported TS2416. They are renamed to
`buildStreamHeaders` / `buildRequestHeaders` / `buildApiHeaders`; the two lmarena test
files that called the helper directly are updated with them.

Worth stating precisely, because the shadow sat on a live dispatch path without being a
live bug: `BaseExecutor.countTokens()` calls `this.buildHeaders(credentials, false)`, and
all three inherit `countTokens()`. It is unreachable today only because
`buildCountTokensUrl()` returns null unless `config.format === "claude"` and the URL
carries `/messages` — hailuo-web and qwen-web set no format, lmarena sets `"openai"` — so
`countTokens()` returns at the guard above. Latent, not live; one `format` change away
from passing a credentials object where a token string is expected.

Two more, surfaced by clearing the above:

* `lmarena` declared `buildUrl` and `transformRequest` `protected` while both are public
  on BaseExecutor (TS2415 — a subclass may widen visibility, never narrow it). Both were
  masked behind the buildHeaders TS2416 and appeared one at a time as it cleared. Runtime
  is unaffected; JavaScript has no member visibility.

* `GithubExecutor.refreshCredentials` had no declared return type, so TypeScript inferred
  the union of its four literal returns. `GheCopilotExecutor` legitimately overrides it
  with a wider `providerSpecificData` (it also records the enterprise proxy URL) and no
  `expiresIn`, which is not assignable to that inferred union. Declared as
  `RefreshedCopilotCredentials | null` — same shape of fix as #8489, on a different method.

Validation: full tsc error-set diff against the base config — 335 -> 331, zero new errors
(line-number-agnostic). `typecheck:core` clean; the 15 existing test files importing a
touched executor pass, including lmarena's 44 across the two updated files.
`plan3-p0.test.ts` fails identically with and without this change (it reads the
developer's real ~/.omniroute DB rather than a test-scoped DATA_DIR).

The new test pins that the inherited method is no longer shadowed — verified to fail on
the base, where all three prototypes still carry their own `buildHeaders` — and that the
`countTokens()` early return which kept it harmless still holds.
2026-07-26 03:51:42 -03:00

91 lines
4.0 KiB
TypeScript

/**
* Guards the executor override signatures fixed for TS 7 readiness.
*
* Three executors declared a *private/protected* `buildHeaders()` helper whose signature
* has nothing to do with `BaseExecutor.buildHeaders(credentials, stream?, clientHeaders?,
* model?, health?)`:
*
* hailuo-web (token: string, yy: string)
* lmarena (_model: string, credentials: unknown, _body: unknown)
* qwen-web (token: string, cookieHeader: string, chatId?: string)
*
* They were name collisions, not overrides — each shadowed the inherited member with an
* incompatible signature (TS2416). `BaseExecutor` calls `this.buildHeaders(credentials,
* false)` from `countTokens()`, so the shadow sat on a live dispatch path; it was never
* reached only because `buildCountTokensUrl()` returns null unless `config.format` is
* `"claude"`, and none of these three is. Latent rather than live — but one `format`
* change away from passing a credentials object where a token string was expected.
*
* The helpers are renamed, so these assertions pin both halves: the inherited method is
* no longer shadowed, and the early return that kept it harmless still holds.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { BaseExecutor } from "../../open-sse/executors/base.ts";
import { HailuoWebExecutor } from "../../open-sse/executors/hailuo-web.ts";
import { LMArenaExecutor } from "../../open-sse/executors/lmarena.ts";
import { QwenWebExecutor } from "../../open-sse/executors/qwen-web.ts";
const CASES = [
{ name: "hailuo-web", make: () => new HailuoWebExecutor(), helper: "buildStreamHeaders" },
{ name: "lmarena", make: () => new LMArenaExecutor(), helper: "buildRequestHeaders" },
{ name: "qwen-web", make: () => new QwenWebExecutor(), helper: "buildApiHeaders" },
];
for (const { name, make, helper } of CASES) {
test(`${name}: buildHeaders resolves to BaseExecutor, not a local helper`, () => {
const executor = make() as unknown as Record<string, unknown>;
assert.equal(
executor.buildHeaders,
BaseExecutor.prototype.buildHeaders,
`${name} must not shadow BaseExecutor.buildHeaders — countTokens() dispatches through it`
);
});
test(`${name}: its own header helper is still present under the renamed key`, () => {
const executor = make() as unknown as Record<string, unknown>;
assert.equal(
typeof executor[helper],
"function",
`${name} should keep its provider-specific header builder as ${helper}()`
);
assert.notEqual(
executor[helper],
BaseExecutor.prototype.buildHeaders,
"the renamed helper must be the provider's own function, not the inherited one"
);
});
test(`${name}: countTokens() short-circuits before reaching buildHeaders`, async () => {
const executor = make();
// buildCountTokensUrl() returns null unless config.format === "claude" and the URL
// carries /messages. That early return is what kept the old shadow unreachable; if it
// ever changes, the inherited buildHeaders must be the one that runs.
const result = await executor.countTokens({
model: "whatever",
body: { messages: [] },
credentials: {},
signal: new AbortController().signal,
log: null,
});
assert.equal(result, null, `${name} does not support the Anthropic count_tokens endpoint`);
});
}
test("LMArenaExecutor does not narrow visibility of inherited members", () => {
// TS2415: a subclass may widen a member's visibility but never narrow it. `buildUrl` and
// `transformRequest` were `protected` here while public on BaseExecutor — masked behind
// the buildHeaders TS2416 until that cleared. Runtime has no visibility, so this asserts
// the members are reachable, which is what the type change encodes.
const executor = new LMArenaExecutor() as unknown as Record<string, unknown>;
for (const member of ["buildUrl", "transformRequest"]) {
assert.equal(typeof executor[member], "function", `${member} must stay callable`);
}
});