






















1-import { describe, expect, it, vi } from "vitest";
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
25import type { OpenClawConfig } from "../config/types.openclaw.js";
36import type { DoctorPrompter } from "./doctor-prompter.js";
4758const authProfileMocks = vi.hoisted(() => ({
6-ensureAuthProfileStore: vi.fn(() => {
9+ensureAuthProfileStore: vi.fn<
10+(
11+agentDir?: string,
12+options?: { allowKeychainPrompt?: boolean },
13+) => {
14+version: number;
15+profiles: Record<
16+string,
17+{ type: "oauth"; provider: string; access: string; refresh: string; expires: number }
18+>;
19+}
20+>(() => {
721throw new Error("unexpected auth profile load");
822}),
9-hasAnyAuthProfileStoreSource: vi.fn(() => false),
23+hasAnyAuthProfileStoreSource: vi.fn((_agentDir?: string) => false),
1024resolveApiKeyForProfile: vi.fn(),
1125resolveProfileUnusableUntilForDisplay: vi.fn(),
1226}));
@@ -20,9 +34,48 @@ vi.mock("../agents/auth-profiles.js", () => ({
20342135vi.mock("../terminal/note.js", () => ({ note: vi.fn() }));
223637+import { note } from "../terminal/note.js";
2338import { noteAuthProfileHealth } from "./doctor-auth.js";
243940+const noteMock = vi.mocked(note);
41+2542describe("noteAuthProfileHealth", () => {
43+let tempDir: string;
44+45+beforeEach(() => {
46+tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-auth-"));
47+authProfileMocks.ensureAuthProfileStore.mockReset();
48+authProfileMocks.hasAnyAuthProfileStoreSource.mockReset();
49+authProfileMocks.hasAnyAuthProfileStoreSource.mockReturnValue(false);
50+authProfileMocks.resolveApiKeyForProfile.mockReset();
51+authProfileMocks.resolveProfileUnusableUntilForDisplay.mockReset();
52+noteMock.mockReset();
53+});
54+55+afterEach(() => {
56+vi.restoreAllMocks();
57+fs.rmSync(tempDir, { recursive: true, force: true });
58+});
59+60+function writeAuthStore(agentDir: string): void {
61+fs.mkdirSync(agentDir, { recursive: true });
62+fs.writeFileSync(path.join(agentDir, "auth-profiles.json"), "{}\n", "utf8");
63+}
64+65+function expiredStore(profileId: string, expires: number) {
66+return {
67+version: 1,
68+profiles: {
69+[profileId]: {
70+type: "oauth" as const,
71+provider: "openai-codex",
72+access: "access",
73+refresh: "refresh",
74+ expires,
75+},
76+},
77+};
78+}
2679it("skips external auth profile resolution when no auth source exists", async () => {
2780await noteAuthProfileHealth({
2881cfg: { channels: { telegram: { enabled: true } } } as OpenClawConfig,
@@ -33,4 +86,110 @@ describe("noteAuthProfileHealth", () => {
3386expect(authProfileMocks.hasAnyAuthProfileStoreSource).toHaveBeenCalledOnce();
3487expect(authProfileMocks.ensureAuthProfileStore).not.toHaveBeenCalled();
3588});
89+90+it("checks the configured default agent auth store source", async () => {
91+const defaultDir = path.join(tempDir, "custom-default");
92+authProfileMocks.hasAnyAuthProfileStoreSource.mockImplementation(
93+(agentDir) => agentDir === defaultDir,
94+);
95+authProfileMocks.ensureAuthProfileStore.mockReturnValue({
96+version: 1,
97+profiles: {},
98+});
99+100+await noteAuthProfileHealth({
101+cfg: {
102+agents: {
103+list: [{ id: "main", default: true, agentDir: defaultDir }],
104+},
105+} as OpenClawConfig,
106+prompter: {} as DoctorPrompter,
107+allowKeychainPrompt: false,
108+});
109+110+expect(authProfileMocks.hasAnyAuthProfileStoreSource).toHaveBeenCalledWith(defaultDir);
111+expect(authProfileMocks.ensureAuthProfileStore).toHaveBeenCalledWith(defaultDir, {
112+allowKeychainPrompt: false,
113+});
114+});
115+116+it("labels model auth diagnostics by agent when multiple agent auth stores are checked", async () => {
117+const now = 1_700_000_000_000;
118+vi.spyOn(Date, "now").mockReturnValue(now);
119+const mainDir = path.join(tempDir, "main-agent");
120+const coderDir = path.join(tempDir, "coder-agent");
121+writeAuthStore(mainDir);
122+writeAuthStore(coderDir);
123+authProfileMocks.hasAnyAuthProfileStoreSource.mockReturnValue(true);
124+authProfileMocks.ensureAuthProfileStore.mockImplementation((agentDir) => {
125+if (agentDir === mainDir) {
126+return expiredStore("openai-codex:main", now - 60_000);
127+}
128+if (agentDir === coderDir) {
129+return expiredStore("openai-codex:coder", now - 60_000);
130+}
131+throw new Error(`unexpected agent dir: ${agentDir ?? "<default>"}`);
132+});
133+134+await noteAuthProfileHealth({
135+cfg: {
136+agents: {
137+list: [
138+{ id: "main", default: true, agentDir: mainDir },
139+{ id: "coder", agentDir: coderDir },
140+],
141+},
142+} as OpenClawConfig,
143+prompter: {
144+confirmAutoFix: vi.fn(async () => false),
145+} as unknown as DoctorPrompter,
146+allowKeychainPrompt: false,
147+});
148+149+expect(noteMock).toHaveBeenCalledWith(
150+expect.stringContaining("openai-codex:main"),
151+"Model auth (agent: main)",
152+);
153+expect(noteMock).toHaveBeenCalledWith(
154+expect.stringContaining("openai-codex:coder"),
155+"Model auth (agent: coder)",
156+);
157+});
158+159+it("passes the target agent dir when refreshing OAuth profiles", async () => {
160+const now = 1_700_000_000_000;
161+vi.spyOn(Date, "now").mockReturnValue(now);
162+const coderDir = path.join(tempDir, "coder-agent");
163+writeAuthStore(coderDir);
164+authProfileMocks.hasAnyAuthProfileStoreSource.mockReturnValue(false);
165+authProfileMocks.ensureAuthProfileStore.mockImplementation((agentDir) => {
166+if (agentDir === coderDir) {
167+return expiredStore("openai-codex:coder", now - 60_000);
168+}
169+return { version: 1, profiles: {} };
170+});
171+authProfileMocks.resolveApiKeyForProfile.mockResolvedValue("token");
172+173+await noteAuthProfileHealth({
174+cfg: {
175+agents: {
176+list: [
177+{ id: "main", default: true, agentDir: path.join(tempDir, "main-agent") },
178+{ id: "coder", agentDir: coderDir },
179+],
180+},
181+} as OpenClawConfig,
182+prompter: {
183+confirmAutoFix: vi.fn(async () => true),
184+} as unknown as DoctorPrompter,
185+allowKeychainPrompt: false,
186+});
187+188+expect(authProfileMocks.resolveApiKeyForProfile).toHaveBeenCalledWith(
189+expect.objectContaining({
190+agentDir: coderDir,
191+profileId: "openai-codex:coder",
192+}),
193+);
194+});
36195});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。