






















@@ -0,0 +1,128 @@
1+import fs from "node:fs";
2+import os from "node:os";
3+import path from "node:path";
4+import { afterEach, describe, expect, it, vi } from "vitest";
5+import { clearRuntimeAuthProfileStoreSnapshots } from "../agents/auth-profiles/store.js";
6+import { maybeRepairLegacyFlatAuthProfileStores } from "./doctor-auth-flat-profiles.js";
7+import type { DoctorPrompter } from "./doctor-prompter.js";
8+9+const roots: string[] = [];
10+11+function makeTempRoot(): string {
12+const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-flat-auth-"));
13+roots.push(root);
14+return root;
15+}
16+17+function makePrompter(shouldRepair: boolean): DoctorPrompter {
18+return {
19+confirm: vi.fn(async () => shouldRepair),
20+confirmAutoFix: vi.fn(async () => shouldRepair),
21+confirmAggressiveAutoFix: vi.fn(async () => shouldRepair),
22+confirmRuntimeRepair: vi.fn(async () => shouldRepair),
23+select: vi.fn(async (_params, fallback) => fallback),
24+ shouldRepair,
25+shouldForce: false,
26+repairMode: {
27+ shouldRepair,
28+shouldForce: false,
29+nonInteractive: false,
30+canPrompt: true,
31+updateInProgress: false,
32+},
33+};
34+}
35+36+function withStateDir<T>(root: string, run: () => T): T {
37+const previousStateDir = process.env.OPENCLAW_STATE_DIR;
38+const previousAgentDir = process.env.OPENCLAW_AGENT_DIR;
39+process.env.OPENCLAW_STATE_DIR = root;
40+delete process.env.OPENCLAW_AGENT_DIR;
41+try {
42+return run();
43+} finally {
44+if (previousStateDir === undefined) {
45+delete process.env.OPENCLAW_STATE_DIR;
46+} else {
47+process.env.OPENCLAW_STATE_DIR = previousStateDir;
48+}
49+if (previousAgentDir === undefined) {
50+delete process.env.OPENCLAW_AGENT_DIR;
51+} else {
52+process.env.OPENCLAW_AGENT_DIR = previousAgentDir;
53+}
54+}
55+}
56+57+afterEach(() => {
58+clearRuntimeAuthProfileStoreSnapshots();
59+for (const root of roots.splice(0)) {
60+fs.rmSync(root, { recursive: true, force: true });
61+}
62+});
63+64+describe("maybeRepairLegacyFlatAuthProfileStores", () => {
65+it("rewrites legacy flat auth-profiles.json stores with a backup", async () => {
66+const root = makeTempRoot();
67+await withStateDir(root, async () => {
68+const agentDir = path.join(root, "agents", "main", "agent");
69+fs.mkdirSync(agentDir, { recursive: true });
70+const authPath = path.join(agentDir, "auth-profiles.json");
71+const legacy = {
72+"ollama-windows": {
73+apiKey: "ollama-local",
74+baseUrl: "http://10.0.2.2:11434/v1",
75+},
76+};
77+fs.writeFileSync(authPath, `${JSON.stringify(legacy)}\n`, "utf8");
78+79+const result = await maybeRepairLegacyFlatAuthProfileStores({
80+cfg: {},
81+prompter: makePrompter(true),
82+now: () => 123,
83+});
84+85+expect(result.detected).toEqual([authPath]);
86+expect(result.changes).toHaveLength(1);
87+expect(result.warnings).toEqual([]);
88+expect(JSON.parse(fs.readFileSync(authPath, "utf8"))).toEqual({
89+version: 1,
90+profiles: {
91+"ollama-windows:default": {
92+type: "api_key",
93+provider: "ollama-windows",
94+key: "ollama-local",
95+},
96+},
97+});
98+expect(JSON.parse(fs.readFileSync(`${authPath}.legacy-flat.123.bak`, "utf8"))).toEqual(
99+legacy,
100+);
101+});
102+});
103+104+it("reports legacy flat stores without rewriting when repair is declined", async () => {
105+const root = makeTempRoot();
106+await withStateDir(root, async () => {
107+const agentDir = path.join(root, "agents", "main", "agent");
108+fs.mkdirSync(agentDir, { recursive: true });
109+const authPath = path.join(agentDir, "auth-profiles.json");
110+const legacy = {
111+openai: {
112+apiKey: "sk-openai",
113+},
114+};
115+fs.writeFileSync(authPath, `${JSON.stringify(legacy)}\n`, "utf8");
116+117+const result = await maybeRepairLegacyFlatAuthProfileStores({
118+cfg: {},
119+prompter: makePrompter(false),
120+});
121+122+expect(result.detected).toEqual([authPath]);
123+expect(result.changes).toEqual([]);
124+expect(result.warnings).toEqual([]);
125+expect(JSON.parse(fs.readFileSync(authPath, "utf8"))).toEqual(legacy);
126+});
127+});
128+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。