

























@@ -10,6 +10,7 @@ import { describe, expect, it } from "vitest";
1010import { resolveOAuthDir } from "../../config/paths.js";
1111import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js";
1212import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js";
13+import { withEnvAsync } from "../../test-utils/env.js";
1314import { AUTH_STORE_VERSION } from "./constants.js";
1415import { loadPersistedAuthProfileStore } from "./persisted.js";
1516import {
@@ -36,6 +37,39 @@ type ExpectedOAuthCredentialFields = {
3637chatgptPlanType?: string;
3738};
383940+type AuthProfileTestState = {
41+stateDir: string;
42+agentDir: string;
43+agentDirFor: (agentId: string) => string;
44+};
45+46+async function withAuthProfileTestState<T>(
47+prefix: string,
48+run: (state: AuthProfileTestState) => Promise<T> | T,
49+options: { clearOAuthDir?: boolean } = {},
50+): Promise<T> {
51+const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
52+const agentDirFor = (agentId: string) => path.join(stateDir, "agents", agentId, "agent");
53+try {
54+return await withEnvAsync(
55+{
56+OPENCLAW_STATE_DIR: stateDir,
57+ ...(options.clearOAuthDir ? { OPENCLAW_OAUTH_DIR: undefined } : {}),
58+},
59+async () =>
60+await run({
61+ stateDir,
62+agentDir: agentDirFor("main"),
63+ agentDirFor,
64+}),
65+);
66+} finally {
67+closeOpenClawAgentDatabasesForTest();
68+closeOpenClawStateDatabaseForTest();
69+fs.rmSync(stateDir, { recursive: true, force: true });
70+}
71+}
72+3973function expectOAuthCredentialFields(
4074value: unknown,
4175expected: ExpectedOAuthCredentialFields,
@@ -64,66 +98,48 @@ function expectOAuthCredentialFields(
64986599describe("promoteAuthProfileInOrder", () => {
66100it("normalizes copied secrets when using the locked upsert path", async () => {
67-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-upsert-"));
68-const agentDir = path.join(stateDir, "agents", "main", "agent");
69-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
70-const previousOAuthDir = process.env.OPENCLAW_OAUTH_DIR;
71-process.env.OPENCLAW_STATE_DIR = stateDir;
72-delete process.env.OPENCLAW_OAUTH_DIR;
73-try {
74-fs.mkdirSync(agentDir, { recursive: true });
101+await withAuthProfileTestState(
102+"openclaw-auth-profile-upsert-",
103+async ({ agentDir }) => {
104+fs.mkdirSync(agentDir, { recursive: true });
105+106+await upsertAuthProfileWithLock({
107+profileId: "openai:manual",
108+credential: {
109+type: "token",
110+provider: "openai",
111+token: " bearer\r\n-token\u2502 ",
112+},
113+ agentDir,
114+});
115+await upsertAuthProfileWithLock({
116+profileId: "anthropic:key",
117+credential: {
118+type: "api_key",
119+provider: "anthropic",
120+key: " sk-\r\nant\u2502 ",
121+},
122+ agentDir,
123+});
7512476-await upsertAuthProfileWithLock({
77-profileId: "openai:manual",
78-credential: {
125+const profiles = loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles;
126+expect(profiles["openai:manual"]).toMatchObject({
79127type: "token",
80128provider: "openai",
81-token: " bearer\r\n-token\u2502 ",
82-},
83- agentDir,
84-});
85-await upsertAuthProfileWithLock({
86-profileId: "anthropic:key",
87-credential: {
129+token: "bearer-token",
130+});
131+expect(profiles["anthropic:key"]).toMatchObject({
88132type: "api_key",
89133provider: "anthropic",
90-key: " sk-\r\nant\u2502 ",
91-},
92- agentDir,
93-});
94-95-const profiles = loadAuthProfileStoreWithoutExternalProfiles(agentDir).profiles;
96-expect(profiles["openai:manual"]).toMatchObject({
97-type: "token",
98-provider: "openai",
99-token: "bearer-token",
100-});
101-expect(profiles["anthropic:key"]).toMatchObject({
102-type: "api_key",
103-provider: "anthropic",
104-key: "sk-ant",
105-});
106-} finally {
107-if (previousStateDir === undefined) {
108-delete process.env.OPENCLAW_STATE_DIR;
109-} else {
110-process.env.OPENCLAW_STATE_DIR = previousStateDir;
111-}
112-if (previousOAuthDir === undefined) {
113-delete process.env.OPENCLAW_OAUTH_DIR;
114-} else {
115-process.env.OPENCLAW_OAUTH_DIR = previousOAuthDir;
116-}
117-fs.rmSync(stateDir, { recursive: true, force: true });
118-}
134+key: "sk-ant",
135+});
136+},
137+{ clearOAuthDir: true },
138+);
119139});
120140121-it("persists openai oauth credentials inline", () => {
122-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-metadata-"));
123-const agentDir = path.join(stateDir, "agents", "main", "agent");
124-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
125-process.env.OPENCLAW_STATE_DIR = stateDir;
126-try {
141+it("persists openai oauth credentials inline", async () => {
142+await withAuthProfileTestState("openclaw-auth-profile-metadata-", ({ agentDir }) => {
127143fs.mkdirSync(agentDir, { recursive: true });
128144const profileId = "openai:default";
129145const expires = Date.now() + 60 * 60 * 1000;
@@ -173,24 +189,11 @@ describe("promoteAuthProfileInOrder", () => {
173189idToken: "local-id-token",
174190},
175191);
176-} finally {
177-if (previousStateDir === undefined) {
178-delete process.env.OPENCLAW_STATE_DIR;
179-} else {
180-process.env.OPENCLAW_STATE_DIR = previousStateDir;
181-}
182-closeOpenClawAgentDatabasesForTest();
183-closeOpenClawStateDatabaseForTest();
184-fs.rmSync(stateDir, { recursive: true, force: true });
185-}
192+});
186193});
187194188-it("preserves access-only openai oauth credentials inline", () => {
189-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-access-only-"));
190-const agentDir = path.join(stateDir, "agents", "main", "agent");
191-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
192-process.env.OPENCLAW_STATE_DIR = stateDir;
193-try {
195+it("preserves access-only openai oauth credentials inline", async () => {
196+await withAuthProfileTestState("openclaw-auth-profile-access-only-", ({ agentDir }) => {
194197fs.mkdirSync(agentDir, { recursive: true });
195198const profileId = "openai:default";
196199const expires = Date.now() + 60 * 60 * 1000;
@@ -226,25 +229,13 @@ describe("promoteAuthProfileInOrder", () => {
226229access: "access-only-token",
227230},
228231);
229-} finally {
230-if (previousStateDir === undefined) {
231-delete process.env.OPENCLAW_STATE_DIR;
232-} else {
233-process.env.OPENCLAW_STATE_DIR = previousStateDir;
234-}
235-closeOpenClawAgentDatabasesForTest();
236-closeOpenClawStateDatabaseForTest();
237-fs.rmSync(stateDir, { recursive: true, force: true });
238-}
232+});
239233});
240234241-it("keeps copied openai oauth profiles inline", () => {
242-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-profile-copy-ref-"));
243-const mainAgentDir = path.join(stateDir, "agents", "main", "agent");
244-const copiedAgentDir = path.join(stateDir, "agents", "copied", "agent");
245-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
246-process.env.OPENCLAW_STATE_DIR = stateDir;
247-try {
235+it("keeps copied openai oauth profiles inline", async () => {
236+await withAuthProfileTestState("openclaw-auth-profile-copy-ref-", ({ agentDirFor }) => {
237+const mainAgentDir = agentDirFor("main");
238+const copiedAgentDir = agentDirFor("copied");
248239fs.mkdirSync(mainAgentDir, { recursive: true });
249240fs.mkdirSync(copiedAgentDir, { recursive: true });
250241const originalProfileId = "openai:default";
@@ -308,24 +299,11 @@ describe("promoteAuthProfileInOrder", () => {
308299access: "copy-access-token",
309300refresh: "copy-refresh-token",
310301});
311-} finally {
312-if (previousStateDir === undefined) {
313-delete process.env.OPENCLAW_STATE_DIR;
314-} else {
315-process.env.OPENCLAW_STATE_DIR = previousStateDir;
316-}
317-closeOpenClawAgentDatabasesForTest();
318-closeOpenClawStateDatabaseForTest();
319-fs.rmSync(stateDir, { recursive: true, force: true });
320-}
302+});
321303});
322304323305it("moves a relogin profile to the front of an existing per-agent provider order", async () => {
324-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-order-promote-"));
325-const agentDir = path.join(stateDir, "agents", "main", "agent");
326-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
327-process.env.OPENCLAW_STATE_DIR = stateDir;
328-try {
306+await withAuthProfileTestState("openclaw-auth-order-promote-", async ({ agentDir }) => {
329307fs.mkdirSync(agentDir, { recursive: true });
330308const newProfileId = "openai:bunsthedev@gmail.com";
331309const staleProfileId = "openai:val@viewdue.ai";
@@ -367,22 +345,11 @@ describe("promoteAuthProfileInOrder", () => {
367345newProfileId,
368346staleProfileId,
369347]);
370-} finally {
371-if (previousStateDir === undefined) {
372-delete process.env.OPENCLAW_STATE_DIR;
373-} else {
374-process.env.OPENCLAW_STATE_DIR = previousStateDir;
375-}
376-fs.rmSync(stateDir, { recursive: true, force: true });
377-}
348+});
378349});
379350380351it("creates a per-agent provider order when relogin has no existing order", async () => {
381-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-order-create-"));
382-const agentDir = path.join(stateDir, "agents", "main", "agent");
383-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
384-process.env.OPENCLAW_STATE_DIR = stateDir;
385-try {
352+await withAuthProfileTestState("openclaw-auth-order-create-", async ({ agentDir }) => {
386353fs.mkdirSync(agentDir, { recursive: true });
387354const newProfileId = "openai:new-login";
388355const primaryProfileId = "openai:primary-login";
@@ -439,22 +406,11 @@ describe("promoteAuthProfileInOrder", () => {
439406backupProfileId,
440407primaryProfileId,
441408]);
442-} finally {
443-if (previousStateDir === undefined) {
444-delete process.env.OPENCLAW_STATE_DIR;
445-} else {
446-process.env.OPENCLAW_STATE_DIR = previousStateDir;
447-}
448-fs.rmSync(stateDir, { recursive: true, force: true });
449-}
409+});
450410});
451411452412it("preserves config-only fallback ids when creating a relogin order", async () => {
453-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-order-config-only-"));
454-const agentDir = path.join(stateDir, "agents", "main", "agent");
455-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
456-process.env.OPENCLAW_STATE_DIR = stateDir;
457-try {
413+await withAuthProfileTestState("openclaw-auth-order-config-only-", async ({ agentDir }) => {
458414fs.mkdirSync(agentDir, { recursive: true });
459415const newProfileId = "openai:new-login";
460416const existingProfileId = "openai:old-login";
@@ -501,22 +457,11 @@ describe("promoteAuthProfileInOrder", () => {
501457existingProfileId,
502458configOnlyProfileId,
503459]);
504-} finally {
505-if (previousStateDir === undefined) {
506-delete process.env.OPENCLAW_STATE_DIR;
507-} else {
508-process.env.OPENCLAW_STATE_DIR = previousStateDir;
509-}
510-fs.rmSync(stateDir, { recursive: true, force: true });
511-}
460+});
512461});
513462514463it("keeps implicit round-robin when relogin has no existing order by default", async () => {
515-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-order-implicit-"));
516-const agentDir = path.join(stateDir, "agents", "main", "agent");
517-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
518-process.env.OPENCLAW_STATE_DIR = stateDir;
519-try {
464+await withAuthProfileTestState("openclaw-auth-order-implicit-", async ({ agentDir }) => {
520465fs.mkdirSync(agentDir, { recursive: true });
521466const newProfileId = "openai:new-login";
522467saveAuthProfileStore(
@@ -543,22 +488,11 @@ describe("promoteAuthProfileInOrder", () => {
543488544489expect(updated?.order?.["openai"]).toBeUndefined();
545490expect(loadAuthProfileStoreForRuntime(agentDir).order?.["openai"]).toBeUndefined();
546-} finally {
547-if (previousStateDir === undefined) {
548-delete process.env.OPENCLAW_STATE_DIR;
549-} else {
550-process.env.OPENCLAW_STATE_DIR = previousStateDir;
551-}
552-fs.rmSync(stateDir, { recursive: true, force: true });
553-}
491+});
554492});
555493556494it("clears matching lastGood after a stale refresh_token_reused profile", async () => {
557-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-clear-lastgood-"));
558-const agentDir = path.join(stateDir, "agents", "main", "agent");
559-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
560-process.env.OPENCLAW_STATE_DIR = stateDir;
561-try {
495+await withAuthProfileTestState("openclaw-auth-clear-lastgood-", async ({ agentDir }) => {
562496fs.mkdirSync(agentDir, { recursive: true });
563497const staleProfileId = "openai:default";
564498saveAuthProfileStore(
@@ -585,22 +519,11 @@ describe("promoteAuthProfileInOrder", () => {
585519});
586520587521expect(loadAuthProfileStoreForRuntime(agentDir).lastGood).toBeUndefined();
588-} finally {
589-if (previousStateDir === undefined) {
590-delete process.env.OPENCLAW_STATE_DIR;
591-} else {
592-process.env.OPENCLAW_STATE_DIR = previousStateDir;
593-}
594-fs.rmSync(stateDir, { recursive: true, force: true });
595-}
522+});
596523});
597524598525it("does not clear lastGood when the failed profile is not the stored profile", async () => {
599-const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-clear-lastgood-keep-"));
600-const agentDir = path.join(stateDir, "agents", "main", "agent");
601-const previousStateDir = process.env.OPENCLAW_STATE_DIR;
602-process.env.OPENCLAW_STATE_DIR = stateDir;
603-try {
526+await withAuthProfileTestState("openclaw-auth-clear-lastgood-keep-", async ({ agentDir }) => {
604527fs.mkdirSync(agentDir, { recursive: true });
605528const goodProfileId = "openai:user@example.test";
606529saveAuthProfileStore(
@@ -627,13 +550,6 @@ describe("promoteAuthProfileInOrder", () => {
627550});
628551629552expect(loadAuthProfileStoreForRuntime(agentDir).lastGood?.["openai"]).toBe(goodProfileId);
630-} finally {
631-if (previousStateDir === undefined) {
632-delete process.env.OPENCLAW_STATE_DIR;
633-} else {
634-process.env.OPENCLAW_STATE_DIR = previousStateDir;
635-}
636-fs.rmSync(stateDir, { recursive: true, force: true });
637-}
553+});
638554});
639555});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。