



























@@ -64,6 +64,37 @@ function createCodexCredentials(extra: Record<string, unknown> = {}) {
6464};
6565}
666667+function expectFields(value: unknown, expected: Record<string, unknown>): void {
68+expect(value).toBeTypeOf("object");
69+expect(value).not.toBeNull();
70+const record = value as Record<string, unknown>;
71+for (const [key, expectedValue] of Object.entries(expected)) {
72+expect(record[key], key).toEqual(expectedValue);
73+}
74+}
75+76+function expectMockFirstArgFields(mock: unknown, expected: Record<string, unknown>): void {
77+const calls = (mock as { mock?: { calls?: Array<Array<unknown>> } }).mock?.calls ?? [];
78+const [arg] = calls[0] ?? [];
79+expectFields(arg, expected);
80+}
81+82+function expectRuntimeErrorContains(runtime: RuntimeEnv, fragment: string): void {
83+expect(
84+(runtime.error as unknown as { mock?: { calls?: Array<Array<unknown>> } }).mock?.calls?.some(
85+([message]) => String(message).includes(fragment),
86+),
87+`runtime.error contains ${fragment}`,
88+).toBe(true);
89+}
90+91+function expectPromptTextCall(prompter: WizardPrompter): void {
92+const textMock = prompter.text as unknown as { mock?: { calls?: Array<Array<unknown>> } };
93+const [arg] = textMock.mock?.calls?.[0] ?? [];
94+expectFields(arg, { message: "Paste the authorization code (or full redirect URL):" });
95+expect(typeof (arg as { validate?: unknown }).validate).toBe("function");
96+}
97+6798async function startCodexAuth(opts: CodexLoginOptions) {
6899await opts.onAuth({ url: CODEX_AUTHORIZE_URL });
69100expect(opts.onManualCodeInput).toBeTypeOf("function");
@@ -99,9 +130,7 @@ describe("loginOpenAICodexOAuth", () => {
99130100131expect(result).toEqual(creds);
101132expect(mocks.loginOpenAICodex).toHaveBeenCalledOnce();
102-expect(mocks.loginOpenAICodex).toHaveBeenCalledWith(
103-expect.objectContaining({ originator: "openclaw" }),
104-);
133+expectMockFirstArgFields(mocks.loginOpenAICodex, { originator: "openclaw" });
105134expect(spin.stop).toHaveBeenCalledWith("OpenAI OAuth complete");
106135expect(runtime.error).not.toHaveBeenCalled();
107136});
@@ -175,7 +204,7 @@ describe("loginOpenAICodexOAuth", () => {
175204).rejects.toThrow("oauth failed");
176205177206expect(spin.stop).toHaveBeenCalledWith("OpenAI OAuth failed");
178-expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("oauth failed"));
207+expectRuntimeErrorContains(runtime, "oauth failed");
179208expect(prompter.note).toHaveBeenCalledWith(
180209"Trouble with OAuth? See https://docs.openclaw.ai/start/faq",
181210"OAuth help",
@@ -197,7 +226,7 @@ describe("loginOpenAICodexOAuth", () => {
197226).rejects.toThrow(/unsupported_region/i);
198227199228expect(spin.stop).toHaveBeenCalledWith("OpenAI OAuth failed");
200-expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("HTTPS_PROXY"));
229+expectRuntimeErrorContains(runtime, "HTTPS_PROXY");
201230expect(prompter.note).toHaveBeenCalledWith(
202231"Trouble with OAuth? See https://docs.openclaw.ai/start/faq",
203232"OAuth help",
@@ -215,10 +244,7 @@ describe("loginOpenAICodexOAuth", () => {
215244const { result, prompter } = await runCodexOAuth({ isRemote: true });
216245217246expect(result).toEqual(creds);
218-expect(prompter.text).toHaveBeenCalledWith({
219-message: "Paste the authorization code (or full redirect URL):",
220-validate: expect.any(Function),
221-});
247+expectPromptTextCall(prompter);
222248});
223249224250it("waits briefly before prompting for manual input after the local browser flow starts", async () => {
@@ -239,22 +265,18 @@ describe("loginOpenAICodexOAuth", () => {
239265return createCodexCredentials({ manualCode: await manualPromise });
240266});
241267242-await expect(
243-loginOpenAICodexOAuth({
244- prompter,
245- runtime,
246-isRemote: false,
247-openUrl: async () => {},
248-}),
249-).resolves.toMatchObject({
268+const result = await loginOpenAICodexOAuth({
269+ prompter,
270+ runtime,
271+isRemote: false,
272+openUrl: async () => {},
273+});
274+expectFields(result, {
250275access: "access-token",
251276refresh: "refresh-token",
252277});
253278254-expect(prompter.text).toHaveBeenCalledWith({
255-message: "Paste the authorization code (or full redirect URL):",
256-validate: expect.any(Function),
257-});
279+expectPromptTextCall(prompter);
258280expect(spin.stop).toHaveBeenCalledWith("Manual OAuth entry required");
259281expect(spin.stop.mock.invocationCallOrder[0]).toBeLessThan(
260282text.mock.invocationCallOrder[0] ?? 0,
@@ -282,14 +304,13 @@ describe("loginOpenAICodexOAuth", () => {
282304return createCodexCredentials({ manualCode: firstManualCode });
283305});
284306285-await expect(
286-loginOpenAICodexOAuth({
287- prompter,
288- runtime,
289-isRemote: false,
290-openUrl: async () => {},
291-}),
292-).resolves.toMatchObject({
307+const result = await loginOpenAICodexOAuth({
308+ prompter,
309+ runtime,
310+isRemote: false,
311+openUrl: async () => {},
312+});
313+expectFields(result, {
293314access: "access-token",
294315refresh: "refresh-token",
295316});
@@ -317,11 +338,10 @@ describe("loginOpenAICodexOAuth", () => {
317338return createCodexCredentials();
318339});
319340320-await expect(runCodexOAuth({ isRemote: false })).resolves.toMatchObject({
321-result: expect.objectContaining({
322-access: "access-token",
323-refresh: "refresh-token",
324-}),
341+const callbackResult = await runCodexOAuth({ isRemote: false });
342+expectFields(callbackResult.result, {
343+access: "access-token",
344+refresh: "refresh-token",
325345});
326346327347expect(vi.getTimerCount()).toBe(0);
@@ -387,22 +407,18 @@ describe("loginOpenAICodexOAuth", () => {
387407},
388408);
389409390-await expect(
391-loginOpenAICodexOAuth({
392- prompter,
393- runtime,
394-isRemote: false,
395-openUrl: async () => {},
396-}),
397-).resolves.toMatchObject({
410+const result = await loginOpenAICodexOAuth({
411+ prompter,
412+ runtime,
413+isRemote: false,
414+openUrl: async () => {},
415+});
416+expectFields(result, {
398417access: "access-token",
399418refresh: "refresh-token",
400419});
401420402-expect(prompter.text).toHaveBeenCalledWith({
403-message: "Paste the authorization code (or full redirect URL):",
404-validate: expect.any(Function),
405-});
421+expectPromptTextCall(prompter);
406422expect(spin.stop).toHaveBeenCalledWith("Manual OAuth entry required");
407423expect(spin.stop.mock.invocationCallOrder[0]).toBeLessThan(
408424text.mock.invocationCallOrder[0] ?? 0,
@@ -425,14 +441,13 @@ describe("loginOpenAICodexOAuth", () => {
425441return createCodexCredentials({ manualCode: firstManualCode });
426442});
427443428-await expect(
429-loginOpenAICodexOAuth({
430- prompter,
431- runtime,
432-isRemote: false,
433-openUrl: async () => {},
434-}),
435-).resolves.toMatchObject({
444+const result = await loginOpenAICodexOAuth({
445+ prompter,
446+ runtime,
447+isRemote: false,
448+openUrl: async () => {},
449+});
450+expectFields(result, {
436451access: "access-token",
437452refresh: "refresh-token",
438453});
@@ -461,14 +476,13 @@ describe("loginOpenAICodexOAuth", () => {
461476return createCodexCredentials();
462477});
463478464-await expect(
465-loginOpenAICodexOAuth({
466- prompter,
467- runtime,
468-isRemote: false,
469-openUrl: async () => {},
470-}),
471-).resolves.toMatchObject({
479+const result = await loginOpenAICodexOAuth({
480+ prompter,
481+ runtime,
482+isRemote: false,
483+openUrl: async () => {},
484+});
485+expectFields(result, {
472486access: "access-token",
473487refresh: "refresh-token",
474488});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。