
























@@ -0,0 +1,275 @@
1+import fs from "node:fs/promises";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, describe, expect, it } from "vitest";
5+import { writeQaAuthProfiles } from "./auth-store.js";
6+7+const tempDirs: string[] = [];
8+9+async function createTempDir(): Promise<string> {
10+const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-qa-auth-store-"));
11+tempDirs.push(dir);
12+return dir;
13+}
14+15+describe("QA auth profile store", () => {
16+afterEach(async () => {
17+await Promise.all(
18+tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })),
19+);
20+});
21+22+it("writes a new auth profile file when none exists", async () => {
23+const agentDir = await createTempDir();
24+25+await writeQaAuthProfiles({
26+ agentDir,
27+profiles: {
28+"qa-mock-openai": {
29+type: "api_key",
30+provider: "openai",
31+key: "qa-mock-not-a-real-key",
32+},
33+},
34+});
35+36+await expect(fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf8")).resolves.toContain(
37+"qa-mock-openai",
38+);
39+});
40+41+it("does not replace corrupt auth profile files", async () => {
42+const agentDir = await createTempDir();
43+const authPath = path.join(agentDir, "auth-profiles.json");
44+await fs.writeFile(authPath, "{not-json", "utf8");
45+46+await expect(
47+writeQaAuthProfiles({
48+ agentDir,
49+profiles: {
50+"qa-mock-openai": {
51+type: "api_key",
52+provider: "openai",
53+key: "qa-mock-not-a-real-key",
54+},
55+},
56+}),
57+).rejects.toThrow();
58+await expect(fs.readFile(authPath, "utf8")).resolves.toBe("{not-json");
59+});
60+61+it("does not merge malformed auth profile shapes", async () => {
62+const agentDir = await createTempDir();
63+const authPath = path.join(agentDir, "auth-profiles.json");
64+const original = JSON.stringify({ version: 1, profiles: { broken: "token" } });
65+await fs.writeFile(authPath, original, "utf8");
66+67+await expect(
68+writeQaAuthProfiles({
69+ agentDir,
70+profiles: {
71+"qa-mock-openai": {
72+type: "api_key",
73+provider: "openai",
74+key: "qa-mock-not-a-real-key",
75+},
76+},
77+}),
78+).rejects.toThrow("Invalid QA auth profiles file");
79+await expect(fs.readFile(authPath, "utf8")).resolves.toBe(original);
80+});
81+82+it("preserves existing ref-backed auth profile shapes", async () => {
83+const agentDir = await createTempDir();
84+const authPath = path.join(agentDir, "auth-profiles.json");
85+await fs.writeFile(
86+authPath,
87+`${JSON.stringify({
88+ version: 1,
89+ profiles: {
90+ existing: {
91+ type: "api_key",
92+ provider: "openai",
93+ keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
94+ },
95+ },
96+ })}\n`,
97+"utf8",
98+);
99+100+await writeQaAuthProfiles({
101+ agentDir,
102+profiles: {
103+"qa-mock-anthropic": {
104+type: "api_key",
105+provider: "anthropic",
106+key: "qa-mock-not-a-real-key",
107+},
108+},
109+});
110+111+const written = JSON.parse(await fs.readFile(authPath, "utf8")) as {
112+profiles?: Record<string, unknown>;
113+};
114+expect(written.profiles?.existing).toEqual({
115+type: "api_key",
116+provider: "openai",
117+keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
118+});
119+expect(written.profiles?.["qa-mock-anthropic"]).toMatchObject({
120+type: "api_key",
121+provider: "anthropic",
122+});
123+});
124+125+it("preserves existing token and oauth auth profile shapes", async () => {
126+const agentDir = await createTempDir();
127+const authPath = path.join(agentDir, "auth-profiles.json");
128+await fs.writeFile(
129+authPath,
130+`${JSON.stringify({
131+ version: 1,
132+ profiles: {
133+ tokenProfile: {
134+ type: "token",
135+ provider: "github",
136+ token: { source: "file", provider: "vault", id: "github/token" },
137+ },
138+ oauthProfile: {
139+ type: "oauth",
140+ provider: "chatgpt",
141+ access: "qa-access-token",
142+ refresh: "qa-refresh-token",
143+ expires: 1_900_000_000_000,
144+ },
145+ legacyOAuthProfile: {
146+ type: "oauth",
147+ provider: "openai-codex",
148+ expires: 1_900_000_000_000,
149+ oauthRef: {
150+ source: "openclaw-credentials",
151+ provider: "openai-codex",
152+ id: "0123456789abcdef0123456789abcdef",
153+ },
154+ },
155+ },
156+ })}\n`,
157+"utf8",
158+);
159+160+await writeQaAuthProfiles({
161+ agentDir,
162+profiles: {
163+"qa-mock-openai": {
164+type: "api_key",
165+provider: "openai",
166+key: "qa-mock-not-a-real-key",
167+},
168+},
169+});
170+171+const written = JSON.parse(await fs.readFile(authPath, "utf8")) as {
172+profiles?: Record<string, unknown>;
173+};
174+expect(written.profiles?.tokenProfile).toEqual({
175+type: "token",
176+provider: "github",
177+token: { source: "file", provider: "vault", id: "github/token" },
178+});
179+expect(written.profiles?.oauthProfile).toEqual({
180+type: "oauth",
181+provider: "chatgpt",
182+access: "qa-access-token",
183+refresh: "qa-refresh-token",
184+expires: 1_900_000_000_000,
185+});
186+expect(written.profiles?.legacyOAuthProfile).toEqual({
187+type: "oauth",
188+provider: "openai-codex",
189+expires: 1_900_000_000_000,
190+oauthRef: {
191+source: "openclaw-credentials",
192+provider: "openai-codex",
193+id: "0123456789abcdef0123456789abcdef",
194+},
195+});
196+});
197+198+it("preserves existing providerless secret refs", async () => {
199+const agentDir = await createTempDir();
200+const authPath = path.join(agentDir, "auth-profiles.json");
201+await fs.writeFile(
202+authPath,
203+`${JSON.stringify({
204+ version: 1,
205+ profiles: {
206+ existing: {
207+ type: "api_key",
208+ provider: "openai",
209+ keyRef: { source: "env", id: "OPENAI_API_KEY" },
210+ },
211+ },
212+ })}\n`,
213+"utf8",
214+);
215+216+await writeQaAuthProfiles({
217+ agentDir,
218+profiles: {
219+"qa-mock-anthropic": {
220+type: "api_key",
221+provider: "anthropic",
222+key: "qa-mock-not-a-real-key",
223+},
224+},
225+});
226+227+const written = JSON.parse(await fs.readFile(authPath, "utf8")) as {
228+profiles?: Record<string, unknown>;
229+};
230+expect(written.profiles?.existing).toEqual({
231+type: "api_key",
232+provider: "openai",
233+keyRef: { source: "env", id: "OPENAI_API_KEY" },
234+});
235+});
236+237+it("preserves existing legacy api key alias profiles", async () => {
238+const agentDir = await createTempDir();
239+const authPath = path.join(agentDir, "auth-profiles.json");
240+await fs.writeFile(
241+authPath,
242+`${JSON.stringify({
243+ version: 1,
244+ profiles: {
245+ existing: {
246+ mode: "api_key",
247+ provider: "openai",
248+ apiKey: "qa-existing-key",
249+ },
250+ },
251+ })}\n`,
252+"utf8",
253+);
254+255+await writeQaAuthProfiles({
256+ agentDir,
257+profiles: {
258+"qa-mock-anthropic": {
259+type: "api_key",
260+provider: "anthropic",
261+key: "qa-mock-not-a-real-key",
262+},
263+},
264+});
265+266+const written = JSON.parse(await fs.readFile(authPath, "utf8")) as {
267+profiles?: Record<string, unknown>;
268+};
269+expect(written.profiles?.existing).toEqual({
270+mode: "api_key",
271+provider: "openai",
272+apiKey: "qa-existing-key",
273+});
274+});
275+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。