import path from "node:path"; import { describe, expect, it, test, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import type { RuntimeEnv } from "../runtime.js"; import { buildCleanupPlan, removeStateAndLinkedPaths, removeWorkspaceDirs, } from "./cleanup-utils.js"; import { applyAgentDefaultPrimaryModel } from "./model-default.js"; describe("buildCleanupPlan", () => { test("resolves inside-state flags and workspace dirs", () => { const tmpRoot = path.join(path.parse(process.cwd()).root, "tmp"); const cfg = { agents: { defaults: { workspace: path.join(tmpRoot, "openclaw-workspace-1") }, list: [{ workspace: path.join(tmpRoot, "openclaw-workspace-2") }], }, }; const plan = buildCleanupPlan({ cfg: cfg as unknown as OpenClawConfig, stateDir: path.join(tmpRoot, "openclaw-state"), configPath: path.join(tmpRoot, "openclaw-state", "openclaw.json"), oauthDir: path.join(tmpRoot, "openclaw-oauth"), }); expect(plan.configInsideState).toBe(true); expect(plan.oauthInsideState).toBe(false); expect(new Set(plan.workspaceDirs)).toEqual( new Set([ path.join(tmpRoot, "openclaw-workspace-1"), path.join(tmpRoot, "openclaw-workspace-2"), ]), ); }); }); describe("applyAgentDefaultPrimaryModel", () => { it("does not mutate when already set", () => { const cfg = { agents: { defaults: { model: { primary: "a/b" } } } } as OpenClawConfig; const result = applyAgentDefaultPrimaryModel({ cfg, model: "a/b" }); expect(result.changed).toBe(false); expect(result.next).toBe(cfg); }); it("normalizes legacy models", () => { const cfg = { agents: { defaults: { model: { primary: "legacy" } } } } as OpenClawConfig; const result = applyAgentDefaultPrimaryModel({ cfg, model: "a/b", legacyModels: new Set(["legacy"]), }); expect(result.changed).toBe(false); expect(result.next).toBe(cfg); }); }); describe("cleanup path removals", () => { function createRuntimeMock() { return { log: vi.fn<(message: string) => void>(), error: vi.fn<(message: string) => void>(), } as unknown as RuntimeEnv & { log: ReturnType void>>; error: ReturnType void>>; }; } it("removes state and only linked paths outside state", async () => { const runtime = createRuntimeMock(); const tmpRoot = path.join(path.parse(process.cwd()).root, "tmp", "openclaw-cleanup"); await removeStateAndLinkedPaths( { stateDir: path.join(tmpRoot, "state"), configPath: path.join(tmpRoot, "state", "openclaw.json"), oauthDir: path.join(tmpRoot, "oauth"), configInsideState: true, oauthInsideState: false, }, runtime, { dryRun: true }, ); const joinedLogs = runtime.log.mock.calls .map(([line]) => line.replaceAll("\\", "/")) .join("\n"); expect(joinedLogs).toContain("/tmp/openclaw-cleanup/state"); expect(joinedLogs).toContain("/tmp/openclaw-cleanup/oauth"); expect(joinedLogs).not.toContain("openclaw.json"); }); it("removes every workspace directory", async () => { const runtime = createRuntimeMock(); const workspaces = ["/tmp/openclaw-workspace-1", "/tmp/openclaw-workspace-2"]; await removeWorkspaceDirs(workspaces, runtime, { dryRun: true }); const logs = runtime.log.mock.calls.map(([line]) => line); expect(logs).toContain("[dry-run] remove /tmp/openclaw-workspace-1"); expect(logs).toContain("[dry-run] remove /tmp/openclaw-workspace-2"); }); });