





















@@ -1,4 +1,5 @@
11import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
2+import { WebSocketServer, type RawData } from "ws";
23import { CodexAppServerClient, MIN_CODEX_APP_SERVER_VERSION } from "./client.js";
34import { createClientHarness } from "./test-support.js";
45@@ -16,6 +17,7 @@ vi.mock("openclaw/plugin-sdk/provider-auth", () => ({
1617}));
17181819let listCodexAppServerModels: typeof import("./models.js").listCodexAppServerModels;
20+let clearSharedCodexAppServerClient: typeof import("./shared-client.js").clearSharedCodexAppServerClient;
1921let resetSharedCodexAppServerClientForTests: typeof import("./shared-client.js").resetSharedCodexAppServerClientForTests;
20222123async function sendInitializeResult(
@@ -36,7 +38,8 @@ async function sendEmptyModelList(harness: ReturnType<typeof createClientHarness
3638describe("shared Codex app-server client", () => {
3739beforeAll(async () => {
3840({ listCodexAppServerModels } = await import("./models.js"));
39-({ resetSharedCodexAppServerClientForTests } = await import("./shared-client.js"));
41+({ clearSharedCodexAppServerClient, resetSharedCodexAppServerClientForTests } =
42+await import("./shared-client.js"));
4043});
41444245afterEach(() => {
@@ -187,4 +190,77 @@ describe("shared Codex app-server client", () => {
187190188191expect(second.process.kill).not.toHaveBeenCalled();
189192});
193+194+it("uses a fresh websocket Authorization header after shared-client token rotation", async () => {
195+const server = new WebSocketServer({ host: "127.0.0.1", port: 0 });
196+const authHeaders: Array<string | undefined> = [];
197+server.on("connection", (socket, request) => {
198+authHeaders.push(request.headers.authorization);
199+socket.on("message", (data) => {
200+const message = JSON.parse(rawDataToText(data)) as { id?: number; method?: string };
201+if (message.method === "initialize") {
202+socket.send(
203+JSON.stringify({ id: message.id, result: { userAgent: "openclaw/0.118.0" } }),
204+);
205+return;
206+}
207+if (message.method === "model/list") {
208+socket.send(JSON.stringify({ id: message.id, result: { data: [] } }));
209+}
210+});
211+});
212+213+try {
214+await new Promise<void>((resolve) => server.once("listening", resolve));
215+const address = server.address();
216+if (!address || typeof address === "string") {
217+throw new Error("expected websocket test server port");
218+}
219+const url = `ws://127.0.0.1:${address.port}`;
220+221+await expect(
222+listCodexAppServerModels({
223+timeoutMs: 1000,
224+startOptions: {
225+transport: "websocket",
226+command: "codex",
227+args: [],
228+ url,
229+authToken: "tok-first",
230+headers: {},
231+},
232+}),
233+).resolves.toEqual({ models: [] });
234+await expect(
235+listCodexAppServerModels({
236+timeoutMs: 1000,
237+startOptions: {
238+transport: "websocket",
239+command: "codex",
240+args: [],
241+ url,
242+authToken: "tok-second",
243+headers: {},
244+},
245+}),
246+).resolves.toEqual({ models: [] });
247+248+expect(authHeaders).toEqual(["Bearer tok-first", "Bearer tok-second"]);
249+} finally {
250+clearSharedCodexAppServerClient();
251+await new Promise<void>((resolve, reject) =>
252+server.close((error) => (error ? reject(error) : resolve())),
253+);
254+}
255+});
190256});
257+258+function rawDataToText(data: RawData): string {
259+if (Array.isArray(data)) {
260+return Buffer.concat(data).toString("utf8");
261+}
262+if (data instanceof ArrayBuffer) {
263+return Buffer.from(new Uint8Array(data)).toString("utf8");
264+}
265+return Buffer.from(data).toString("utf8");
266+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。