mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 06:20:44 +00:00
* fix(agents): refresh bootstrap snapshot when workspace files change * fix(clownfish): address review for ghcrawl-207042-agentic-merge (1)
134 lines
5.0 KiB
TypeScript
134 lines
5.0 KiB
TypeScript
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
|
|
|
vi.mock("./workspace.js", () => ({
|
|
loadWorkspaceBootstrapFiles: vi.fn(),
|
|
}));
|
|
|
|
function makeFile(name: string, content: string): WorkspaceBootstrapFile {
|
|
return {
|
|
name: name as WorkspaceBootstrapFile["name"],
|
|
path: `/ws/${name}`,
|
|
content,
|
|
missing: false,
|
|
};
|
|
}
|
|
|
|
describe("getOrLoadBootstrapFiles", () => {
|
|
const files = [makeFile("AGENTS.md", "# Agent"), makeFile("SOUL.md", "# Soul")];
|
|
let clearAllBootstrapSnapshots: typeof import("./bootstrap-cache.js").clearAllBootstrapSnapshots;
|
|
let getOrLoadBootstrapFiles: typeof import("./bootstrap-cache.js").getOrLoadBootstrapFiles;
|
|
let workspaceModule: typeof import("./workspace.js");
|
|
|
|
const mockLoad = () => vi.mocked(workspaceModule.loadWorkspaceBootstrapFiles);
|
|
|
|
beforeAll(async () => {
|
|
({ clearAllBootstrapSnapshots, getOrLoadBootstrapFiles } =
|
|
await import("./bootstrap-cache.js"));
|
|
workspaceModule = await import("./workspace.js");
|
|
});
|
|
|
|
beforeEach(() => {
|
|
clearAllBootstrapSnapshots();
|
|
mockLoad().mockResolvedValue(files);
|
|
});
|
|
|
|
afterEach(() => {
|
|
clearAllBootstrapSnapshots();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("loads from disk on first call and caches", async () => {
|
|
const result = await getOrLoadBootstrapFiles({
|
|
workspaceDir: "/ws",
|
|
sessionKey: "session-1",
|
|
});
|
|
|
|
expect(result).toBe(files);
|
|
expect(mockLoad()).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("refreshes from disk on second call while preserving unchanged object identity", async () => {
|
|
const refreshedFiles = [makeFile("AGENTS.md", "# Agent"), makeFile("SOUL.md", "# Soul")];
|
|
mockLoad().mockResolvedValueOnce(files).mockResolvedValueOnce(refreshedFiles);
|
|
|
|
const first = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "session-1" });
|
|
const result = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "session-1" });
|
|
|
|
expect(first).toBe(files);
|
|
expect(result).toBe(first);
|
|
expect(result).not.toBe(refreshedFiles);
|
|
expect(mockLoad()).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("replaces cached result when workspace bootstrap contents change", async () => {
|
|
const updatedFiles = [makeFile("AGENTS.md", "# Agent v2"), makeFile("SOUL.md", "# Soul")];
|
|
mockLoad().mockResolvedValueOnce(files).mockResolvedValueOnce(updatedFiles);
|
|
|
|
const first = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "session-1" });
|
|
const result = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "session-1" });
|
|
|
|
expect(first).toBe(files);
|
|
expect(result).toBe(updatedFiles);
|
|
expect(mockLoad()).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("different session keys get independent caches", async () => {
|
|
const files2 = [makeFile("AGENTS.md", "# Agent v2")];
|
|
mockLoad().mockResolvedValueOnce(files).mockResolvedValueOnce(files2);
|
|
|
|
const r1 = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "session-1" });
|
|
const r2 = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "session-2" });
|
|
|
|
expect(r1).toBe(files);
|
|
expect(r2).toBe(files2);
|
|
expect(mockLoad()).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
describe("clearBootstrapSnapshot", () => {
|
|
let clearAllBootstrapSnapshots: typeof import("./bootstrap-cache.js").clearAllBootstrapSnapshots;
|
|
let clearBootstrapSnapshot: typeof import("./bootstrap-cache.js").clearBootstrapSnapshot;
|
|
let getOrLoadBootstrapFiles: typeof import("./bootstrap-cache.js").getOrLoadBootstrapFiles;
|
|
let workspaceModule: typeof import("./workspace.js");
|
|
|
|
const mockLoad = () => vi.mocked(workspaceModule.loadWorkspaceBootstrapFiles);
|
|
|
|
beforeAll(async () => {
|
|
({ clearAllBootstrapSnapshots, clearBootstrapSnapshot, getOrLoadBootstrapFiles } =
|
|
await import("./bootstrap-cache.js"));
|
|
workspaceModule = await import("./workspace.js");
|
|
});
|
|
|
|
beforeEach(() => {
|
|
clearAllBootstrapSnapshots();
|
|
mockLoad().mockResolvedValue([makeFile("AGENTS.md", "content")]);
|
|
});
|
|
|
|
afterEach(() => {
|
|
clearAllBootstrapSnapshots();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("clears a single session entry", async () => {
|
|
await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "sk" });
|
|
clearBootstrapSnapshot("sk");
|
|
|
|
// Next call should hit disk again.
|
|
await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "sk" });
|
|
expect(mockLoad()).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("does not affect other sessions", async () => {
|
|
await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "sk1" });
|
|
const first = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "sk2" });
|
|
|
|
clearBootstrapSnapshot("sk1");
|
|
|
|
// sk2 should still preserve its cached snapshot identity after refresh.
|
|
const second = await getOrLoadBootstrapFiles({ workspaceDir: "/ws", sessionKey: "sk2" });
|
|
expect(second).toBe(first);
|
|
expect(mockLoad()).toHaveBeenCalledTimes(3); // sk1 x1, sk2 x2
|
|
});
|
|
});
|