




















@@ -1,56 +1,98 @@
1-import fs from "node:fs";
2-import os from "node:os";
3-import path from "node:path";
4-import { describe, expect, it } from "vitest";
5-import { AUTH_STORE_VERSION } from "./constants.js";
6-import { promoteAuthProfileInOrder } from "./profiles.js";
7-import { loadAuthProfileStoreForRuntime, saveAuthProfileStore } from "./store.js";
8-9-describe("promoteAuthProfileInOrder", () => {
10-it("moves a relogin profile to the front of an existing per-agent provider order", async () => {
11-const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-order-promote-"));
12-try {
13-const newProfileId = "openai-codex:bunsthedev@gmail.com";
14-const staleProfileId = "openai-codex:val@viewdue.ai";
15-saveAuthProfileStore(
16-{
17-version: AUTH_STORE_VERSION,
18-profiles: {
19-[newProfileId]: {
20-type: "oauth",
21-provider: "openai-codex",
22-access: "new-access",
23-refresh: "new-refresh",
24-expires: Date.now() + 60 * 60 * 1000,
25-},
26-[staleProfileId]: {
27-type: "oauth",
28-provider: "openai-codex",
29-access: "stale-access",
30-refresh: "stale-refresh",
31-expires: Date.now() + 30 * 60 * 1000,
32-},
33-},
34-order: {
35-"openai-codex": [staleProfileId],
36-},
37-},
38-agentDir,
39-);
40-41-const updated = await promoteAuthProfileInOrder({
42- agentDir,
43-provider: "openai-codex",
44-profileId: newProfileId,
45-});
46-47-expect(updated?.order?.["openai-codex"]).toEqual([newProfileId, staleProfileId]);
48-expect(loadAuthProfileStoreForRuntime(agentDir).order?.["openai-codex"]).toEqual([
49-newProfileId,
50-staleProfileId,
51-]);
52-} finally {
53-fs.rmSync(agentDir, { recursive: true, force: true });
54-}
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+import { markAuthProfileSuccess } from "./profiles.js";
3+import type { AuthProfileStore } from "./types.js";
4+5+const storeMocks = vi.hoisted(() => ({
6+saveAuthProfileStore: vi.fn(),
7+updateAuthProfileStoreWithLock: vi.fn().mockResolvedValue(null),
8+}));
9+10+vi.mock("./store.js", () => ({
11+ensureAuthProfileStoreForLocalUpdate: vi.fn(() => ({ version: 1, profiles: {} })),
12+saveAuthProfileStore: storeMocks.saveAuthProfileStore,
13+updateAuthProfileStoreWithLock: storeMocks.updateAuthProfileStoreWithLock,
14+}));
15+16+beforeEach(() => {
17+vi.clearAllMocks();
18+storeMocks.updateAuthProfileStoreWithLock.mockResolvedValue(null);
19+});
20+21+function makeStore(usageStats: AuthProfileStore["usageStats"]): AuthProfileStore {
22+return {
23+version: 1,
24+profiles: {
25+"anthropic:default": {
26+type: "api_key",
27+provider: "anthropic",
28+key: "sk-test",
29+},
30+},
31+ usageStats,
32+};
33+}
34+35+describe("markAuthProfileSuccess", () => {
36+it("updates last-good and usage stats through the fallback save path when lock update misses", async () => {
37+const store = makeStore({
38+"anthropic:default": {
39+errorCount: 3,
40+cooldownUntil: Date.now() + 60_000,
41+cooldownReason: "rate_limit",
42+},
43+});
44+45+storeMocks.updateAuthProfileStoreWithLock.mockResolvedValue(null);
46+47+const beforeUsed = Date.now();
48+await markAuthProfileSuccess({
49+ store,
50+provider: "anthropic",
51+profileId: "anthropic:default",
52+agentDir: "/tmp/openclaw-auth-profiles-success",
53+});
54+55+expect(storeMocks.saveAuthProfileStore).toHaveBeenCalledWith(
56+store,
57+"/tmp/openclaw-auth-profiles-success",
58+);
59+expect(store.lastGood).toEqual({ anthropic: "anthropic:default" });
60+expect(store.usageStats?.["anthropic:default"]).toMatchObject({
61+errorCount: 0,
62+cooldownUntil: undefined,
63+cooldownReason: undefined,
64+});
65+expect(store.usageStats?.["anthropic:default"]?.lastUsed).toBeGreaterThanOrEqual(beforeUsed);
66+});
67+68+it("adopts locked store last-good and usage stats without saving locally when lock update succeeds", async () => {
69+const store = makeStore({
70+"anthropic:default": {
71+errorCount: 3,
72+cooldownUntil: Date.now() + 60_000,
73+},
74+});
75+const lockedStore = makeStore(undefined);
76+77+storeMocks.updateAuthProfileStoreWithLock.mockImplementationOnce(async ({ updater }) => {
78+updater(lockedStore);
79+return lockedStore;
80+});
81+82+await markAuthProfileSuccess({
83+ store,
84+provider: "anthropic",
85+profileId: "anthropic:default",
86+agentDir: "/tmp/openclaw-auth-profiles-success",
87+});
88+89+expect(storeMocks.saveAuthProfileStore).not.toHaveBeenCalled();
90+expect(store.lastGood).toEqual({ anthropic: "anthropic:default" });
91+expect(store.usageStats).toEqual(lockedStore.usageStats);
92+expect(store.usageStats?.["anthropic:default"]).toMatchObject({
93+errorCount: 0,
94+cooldownUntil: undefined,
95+});
96+expect(store.usageStats?.["anthropic:default"]?.lastUsed).toEqual(expect.any(Number));
5597});
5698});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。