mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-30 22:20:23 +00:00
refactor(models): reuse validated config snapshot loader
This commit is contained in:
@@ -10,7 +10,6 @@ import { normalizeProviderId } from "../../agents/model-selection.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||
import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import { resolvePluginProviders } from "../../plugins/providers.js";
|
||||
import type { ProviderAuthResult, ProviderPlugin } from "../../plugins/types.js";
|
||||
@@ -28,7 +27,7 @@ import {
|
||||
pickAuthMethod,
|
||||
resolveProviderMatch,
|
||||
} from "../provider-auth-helpers.js";
|
||||
import { updateConfig } from "./shared.js";
|
||||
import { loadValidConfigOrThrow, updateConfig } from "./shared.js";
|
||||
|
||||
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
||||
clackConfirm({
|
||||
@@ -278,13 +277,7 @@ export async function modelsAuthLoginCommand(opts: LoginOptions, runtime: Runtim
|
||||
throw new Error("models auth login requires an interactive TTY.");
|
||||
}
|
||||
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
if (!snapshot.valid) {
|
||||
const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n");
|
||||
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
|
||||
}
|
||||
|
||||
const config = snapshot.config;
|
||||
const config = await loadValidConfigOrThrow();
|
||||
const defaultAgentId = resolveDefaultAgentId(config);
|
||||
const agentDir = resolveAgentDir(config, defaultAgentId);
|
||||
const workspaceDir =
|
||||
|
||||
63
src/commands/models/shared.test.ts
Normal file
63
src/commands/models/shared.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
writeConfigFile: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../config/config.js", () => ({
|
||||
readConfigFileSnapshot: (...args: unknown[]) => mocks.readConfigFileSnapshot(...args),
|
||||
writeConfigFile: (...args: unknown[]) => mocks.writeConfigFile(...args),
|
||||
}));
|
||||
|
||||
import { loadValidConfigOrThrow, updateConfig } from "./shared.js";
|
||||
|
||||
describe("models/shared", () => {
|
||||
beforeEach(() => {
|
||||
mocks.readConfigFileSnapshot.mockReset();
|
||||
mocks.writeConfigFile.mockReset();
|
||||
});
|
||||
|
||||
it("returns config when snapshot is valid", async () => {
|
||||
const cfg = { providers: {} } as unknown as OpenClawConfig;
|
||||
mocks.readConfigFileSnapshot.mockResolvedValue({
|
||||
valid: true,
|
||||
config: cfg,
|
||||
});
|
||||
|
||||
await expect(loadValidConfigOrThrow()).resolves.toBe(cfg);
|
||||
});
|
||||
|
||||
it("throws formatted issues when snapshot is invalid", async () => {
|
||||
mocks.readConfigFileSnapshot.mockResolvedValue({
|
||||
valid: false,
|
||||
path: "/tmp/openclaw.json",
|
||||
issues: [{ path: "providers.openai.apiKey", message: "Required" }],
|
||||
});
|
||||
|
||||
await expect(loadValidConfigOrThrow()).rejects.toThrowError(
|
||||
"Invalid config at /tmp/openclaw.json\n- providers.openai.apiKey: Required",
|
||||
);
|
||||
});
|
||||
|
||||
it("updateConfig writes mutated config", async () => {
|
||||
const cfg = { update: { channel: "stable" } } as unknown as OpenClawConfig;
|
||||
mocks.readConfigFileSnapshot.mockResolvedValue({
|
||||
valid: true,
|
||||
config: cfg,
|
||||
});
|
||||
mocks.writeConfigFile.mockResolvedValue(undefined);
|
||||
|
||||
await updateConfig((current) => ({
|
||||
...current,
|
||||
update: { channel: "beta" },
|
||||
}));
|
||||
|
||||
expect(mocks.writeConfigFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
update: { channel: "beta" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -59,15 +59,20 @@ export const isLocalBaseUrl = (baseUrl: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export async function updateConfig(
|
||||
mutator: (cfg: OpenClawConfig) => OpenClawConfig,
|
||||
): Promise<OpenClawConfig> {
|
||||
export async function loadValidConfigOrThrow(): Promise<OpenClawConfig> {
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
if (!snapshot.valid) {
|
||||
const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n");
|
||||
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
|
||||
}
|
||||
const next = mutator(snapshot.config);
|
||||
return snapshot.config;
|
||||
}
|
||||
|
||||
export async function updateConfig(
|
||||
mutator: (cfg: OpenClawConfig) => OpenClawConfig,
|
||||
): Promise<OpenClawConfig> {
|
||||
const config = await loadValidConfigOrThrow();
|
||||
const next = mutator(config);
|
||||
await writeConfigFile(next);
|
||||
return next;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user