mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 14:22:09 +03:00
GrokCliExecutor.execute() dispatches via raw https.request (nativePost) instead of the shared fetch path, so it never inherited (nor delegated to) BaseExecutor.execute()'s proactive-refresh gate the way codex.ts does via super.execute(). The only refresh that ever fired was the reactive one on a 401/403 from upstream — the rotating xAI refresh_token idled until real expiry, matching the "unusable within minutes, must delete/re-add" report. Wires in the same needsRefresh()/refreshCredentials() gate, using runWithOnPersist + isUnrecoverableRefreshError to keep the [refresh + persist] atomic under the same per-connection mutex Codex/Claude rely on for rotating refresh tokens (base.ts:592-644). Also fixes the smaller, separate bug #2 from the same report: grok-cli was absent from OAUTH_TEST_CONFIG in the connection-test route, so "Test Connection" always reported "Provider test not supported" regardless of token health. Added a checkExpiry entry (same pattern as qwen/cline/ kilocode — Grok Build's proxy doesn't expose a lightweight probe endpoint with the cli-specific headers this shared prober sends). Extracted OAUTH_TEST_CONFIG into its own module (oauthTestConfig.ts) so the new entry doesn't grow the frozen route.ts past its file-size cap. Bug #3 (no browser/device-code login for Grok Build) and bug #4 (quota display) from the same issue are feature gaps, not regressions — left as follow-ups per the triage plan-file. Refs #7610
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { GrokCliExecutor } from "../../open-sse/executors/grok-cli.ts";
|
|
import type { ExecuteInput, ExecutorLog, ProviderCredentials } from "../../open-sse/executors/base.ts";
|
|
|
|
type TestableGrokCliExecutor = {
|
|
execute: (input: ExecuteInput) => Promise<{ response: Response }>;
|
|
refreshCredentials: (
|
|
credentials: ProviderCredentials,
|
|
log?: ExecutorLog | null
|
|
) => Promise<Partial<ProviderCredentials> | null>;
|
|
nativePost: (
|
|
url: string,
|
|
headers: Record<string, string>,
|
|
bodyStr: string,
|
|
signal?: AbortSignal | null
|
|
) => Promise<Response>;
|
|
};
|
|
|
|
test("GrokCliExecutor.execute() proactively refreshes an expired access token (#7610)", async () => {
|
|
const executor = new GrokCliExecutor() as unknown as TestableGrokCliExecutor;
|
|
|
|
// Stub the real network call (nativeHttpsPost → auth.x.ai) so the test never
|
|
// touches the network — only the wiring (does execute() call
|
|
// refreshCredentials() at all, and does the refreshed token reach the
|
|
// outgoing Authorization header) is under test here.
|
|
let refreshCalled = false;
|
|
executor.refreshCredentials = async () => {
|
|
refreshCalled = true;
|
|
return {
|
|
accessToken: "FRESH_ACCESS_TOKEN",
|
|
refreshToken: "rotated-refresh-token",
|
|
expiresAt: new Date(Date.now() + 3600_000).toISOString(),
|
|
};
|
|
};
|
|
|
|
let capturedHeaders: Record<string, string> | null = null;
|
|
executor.nativePost = async (_url, headers) => {
|
|
capturedHeaders = headers;
|
|
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
|
};
|
|
|
|
const expiredAt = new Date(Date.now() - 60_000).toISOString();
|
|
const credentials: ProviderCredentials = {
|
|
accessToken: "STALE_ACCESS_TOKEN",
|
|
refreshToken: "valid-refresh-token",
|
|
expiresAt: expiredAt,
|
|
};
|
|
|
|
await executor.execute({
|
|
model: "grok-composer-2.5-fast",
|
|
body: { messages: [{ role: "user", content: "hi" }] },
|
|
stream: false,
|
|
credentials,
|
|
} as ExecuteInput);
|
|
|
|
assert.equal(
|
|
refreshCalled,
|
|
true,
|
|
"expected GrokCliExecutor.execute() to proactively call refreshCredentials()"
|
|
);
|
|
assert.notEqual(capturedHeaders?.["Authorization"], "Bearer STALE_ACCESS_TOKEN");
|
|
assert.equal(capturedHeaders?.["Authorization"], "Bearer FRESH_ACCESS_TOKEN");
|
|
});
|