test(gateway): dedupe boot workspace setup and cover boot failures

This commit is contained in:
Peter Steinberger
2026-02-21 19:28:36 +00:00
parent 0e49eec056
commit 8f11868cc2

View File

@@ -15,6 +15,11 @@ const { resolveStorePath } = await import("../config/sessions/paths.js");
const { loadSessionStore, saveSessionStore } = await import("../config/sessions/store.js"); const { loadSessionStore, saveSessionStore } = await import("../config/sessions/store.js");
describe("runBootOnce", () => { describe("runBootOnce", () => {
type BootWorkspaceOptions = {
bootAsDirectory?: boolean;
bootContent?: string;
};
const resolveMainStore = ( const resolveMainStore = (
cfg: { cfg: {
session?: { store?: string; scope?: SessionScope; mainKey?: string }; session?: { store?: string; scope?: SessionScope; mainKey?: string };
@@ -42,6 +47,24 @@ describe("runBootOnce", () => {
sendMessageIMessage: vi.fn(), sendMessageIMessage: vi.fn(),
}); });
const withBootWorkspace = async (
options: BootWorkspaceOptions,
run: (workspaceDir: string) => Promise<void>,
) => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
try {
const bootPath = path.join(workspaceDir, "BOOT.md");
if (options.bootAsDirectory) {
await fs.mkdir(bootPath, { recursive: true });
} else if (typeof options.bootContent === "string") {
await fs.writeFile(bootPath, options.bootContent, "utf-8");
}
await run(workspaceDir);
} finally {
await fs.rm(workspaceDir, { recursive: true, force: true });
}
};
const mockAgentUpdatesMainSession = (storePath: string, sessionKey: string) => { const mockAgentUpdatesMainSession = (storePath: string, sessionKey: string) => {
agentCommand.mockImplementation(async (opts: { sessionId?: string }) => { agentCommand.mockImplementation(async (opts: { sessionId?: string }) => {
const current = loadSessionStore(storePath, { skipCache: true }); const current = loadSessionStore(storePath, { skipCache: true });
@@ -54,166 +77,173 @@ describe("runBootOnce", () => {
}; };
it("skips when BOOT.md is missing", async () => { it("skips when BOOT.md is missing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-")); await withBootWorkspace({}, async (workspaceDir) => {
await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({ await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "skipped", status: "skipped",
reason: "missing", reason: "missing",
});
expect(agentCommand).not.toHaveBeenCalled();
});
});
it("returns failed when BOOT.md cannot be read", async () => {
await withBootWorkspace({ bootAsDirectory: true }, async (workspaceDir) => {
const result = await runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir });
expect(result.status).toBe("failed");
if (result.status === "failed") {
expect(result.reason.length).toBeGreaterThan(0);
}
expect(agentCommand).not.toHaveBeenCalled();
}); });
expect(agentCommand).not.toHaveBeenCalled();
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
it.each([ it.each([
{ title: "empty", content: " \n", reason: "empty" as const }, { title: "empty", content: " \n", reason: "empty" as const },
{ title: "whitespace-only", content: "\n\t ", reason: "empty" as const }, { title: "whitespace-only", content: "\n\t ", reason: "empty" as const },
])("skips when BOOT.md is $title", async ({ content, reason }) => { ])("skips when BOOT.md is $title", async ({ content, reason }) => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-")); await withBootWorkspace({ bootContent: content }, async (workspaceDir) => {
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8"); await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({
await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({ status: "skipped",
status: "skipped", reason,
reason, });
expect(agentCommand).not.toHaveBeenCalled();
}); });
expect(agentCommand).not.toHaveBeenCalled();
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
it("runs agent command when BOOT.md exists", async () => { it("runs agent command when BOOT.md exists", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
const content = "Say hello when you wake up."; const content = "Say hello when you wake up.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8"); await withBootWorkspace({ bootContent: content }, async (workspaceDir) => {
agentCommand.mockResolvedValue(undefined);
await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran",
});
agentCommand.mockResolvedValue(undefined); expect(agentCommand).toHaveBeenCalledTimes(1);
await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({ const call = agentCommand.mock.calls[0]?.[0];
status: "ran", expect(call).toEqual(
expect.objectContaining({
deliver: false,
sessionKey: resolveMainSessionKey({}),
}),
);
expect(call?.message).toContain("BOOT.md:");
expect(call?.message).toContain(content);
expect(call?.message).toContain("NO_REPLY");
}); });
});
expect(agentCommand).toHaveBeenCalledTimes(1); it("returns failed when agent command throws", async () => {
const call = agentCommand.mock.calls[0]?.[0]; await withBootWorkspace({ bootContent: "Wake up and report." }, async (workspaceDir) => {
expect(call).toEqual( agentCommand.mockRejectedValue(new Error("boom"));
expect.objectContaining({ await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({
deliver: false, status: "failed",
sessionKey: resolveMainSessionKey({}), reason: expect.stringContaining("agent run failed: boom"),
}), });
); expect(agentCommand).toHaveBeenCalledTimes(1);
expect(call?.message).toContain("BOOT.md:"); });
expect(call?.message).toContain(content);
expect(call?.message).toContain("NO_REPLY");
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
it("uses per-agent session key when agentId is provided", async () => { it("uses per-agent session key when agentId is provided", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-")); await withBootWorkspace({ bootContent: "Check status." }, async (workspaceDir) => {
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), "Check status.", "utf-8"); agentCommand.mockResolvedValue(undefined);
const cfg = {};
const agentId = "ops";
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir, agentId })).resolves.toEqual({
status: "ran",
});
agentCommand.mockResolvedValue(undefined); expect(agentCommand).toHaveBeenCalledTimes(1);
const cfg = {}; const perAgentCall = agentCommand.mock.calls[0]?.[0];
const agentId = "ops"; expect(perAgentCall?.sessionKey).toBe(resolveAgentMainSessionKey({ cfg, agentId }));
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir, agentId })).resolves.toEqual({
status: "ran",
}); });
expect(agentCommand).toHaveBeenCalledTimes(1);
const perAgentCall = agentCommand.mock.calls[0]?.[0];
expect(perAgentCall?.sessionKey).toBe(resolveAgentMainSessionKey({ cfg, agentId }));
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
it("generates new session ID when no existing session exists", async () => { it("generates new session ID when no existing session exists", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
const content = "Say hello when you wake up."; const content = "Say hello when you wake up.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8"); await withBootWorkspace({ bootContent: content }, async (workspaceDir) => {
agentCommand.mockResolvedValue(undefined);
const cfg = {};
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran",
});
agentCommand.mockResolvedValue(undefined); expect(agentCommand).toHaveBeenCalledTimes(1);
const cfg = {}; const call = agentCommand.mock.calls[0]?.[0];
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran", // Verify a boot-style session ID was generated (format: boot-YYYY-MM-DD_HH-MM-SS-xxx-xxxxxxxx)
expect(call?.sessionId).toMatch(
/^boot-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{8}$/,
);
}); });
expect(agentCommand).toHaveBeenCalledTimes(1);
const call = agentCommand.mock.calls[0]?.[0];
// Verify a boot-style session ID was generated (format: boot-YYYY-MM-DD_HH-MM-SS-xxx-xxxxxxxx)
expect(call?.sessionId).toMatch(/^boot-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{8}$/);
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
it("uses a fresh boot session ID even when main session mapping already exists", async () => { it("uses a fresh boot session ID even when main session mapping already exists", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
const content = "Say hello when you wake up."; const content = "Say hello when you wake up.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8"); await withBootWorkspace({ bootContent: content }, async (workspaceDir) => {
const cfg = {};
const { sessionKey, storePath } = resolveMainStore(cfg);
const existingSessionId = "main-session-abc123";
const cfg = {}; await saveSessionStore(storePath, {
const { sessionKey, storePath } = resolveMainStore(cfg); [sessionKey]: {
const existingSessionId = "main-session-abc123"; sessionId: existingSessionId,
updatedAt: Date.now(),
},
});
await saveSessionStore(storePath, { agentCommand.mockResolvedValue(undefined);
[sessionKey]: { await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
sessionId: existingSessionId, status: "ran",
updatedAt: Date.now(), });
},
expect(agentCommand).toHaveBeenCalledTimes(1);
const call = agentCommand.mock.calls[0]?.[0];
expect(call?.sessionId).not.toBe(existingSessionId);
expect(call?.sessionId).toMatch(
/^boot-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{8}$/,
);
expect(call?.sessionKey).toBe(sessionKey);
}); });
agentCommand.mockResolvedValue(undefined);
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran",
});
expect(agentCommand).toHaveBeenCalledTimes(1);
const call = agentCommand.mock.calls[0]?.[0];
expect(call?.sessionId).not.toBe(existingSessionId);
expect(call?.sessionId).toMatch(/^boot-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-\d{3}-[0-9a-f]{8}$/);
expect(call?.sessionKey).toBe(sessionKey);
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
it("restores the original main session mapping after the boot run", async () => { it("restores the original main session mapping after the boot run", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
const content = "Check if the system is healthy."; const content = "Check if the system is healthy.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8"); await withBootWorkspace({ bootContent: content }, async (workspaceDir) => {
const cfg = {};
const { sessionKey, storePath } = resolveMainStore(cfg);
const existingSessionId = "main-session-xyz789";
const cfg = {}; await saveSessionStore(storePath, {
const { sessionKey, storePath } = resolveMainStore(cfg); [sessionKey]: {
const existingSessionId = "main-session-xyz789"; sessionId: existingSessionId,
updatedAt: Date.now() - 60_000, // 1 minute ago
},
});
await saveSessionStore(storePath, { mockAgentUpdatesMainSession(storePath, sessionKey);
[sessionKey]: { await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
sessionId: existingSessionId, status: "ran",
updatedAt: Date.now() - 60_000, // 1 minute ago });
},
const restored = loadSessionStore(storePath, { skipCache: true });
expect(restored[sessionKey]?.sessionId).toBe(existingSessionId);
}); });
mockAgentUpdatesMainSession(storePath, sessionKey);
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran",
});
const restored = loadSessionStore(storePath, { skipCache: true });
expect(restored[sessionKey]?.sessionId).toBe(existingSessionId);
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
it("removes a boot-created main-session mapping when none existed before", async () => { it("removes a boot-created main-session mapping when none existed before", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-")); await withBootWorkspace({ bootContent: "health check" }, async (workspaceDir) => {
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), "health check", "utf-8"); const cfg = {};
const { sessionKey, storePath } = resolveMainStore(cfg);
const cfg = {}; mockAgentUpdatesMainSession(storePath, sessionKey);
const { sessionKey, storePath } = resolveMainStore(cfg);
mockAgentUpdatesMainSession(storePath, sessionKey); await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran",
});
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({ const restored = loadSessionStore(storePath, { skipCache: true });
status: "ran", expect(restored[sessionKey]).toBeUndefined();
}); });
const restored = loadSessionStore(storePath, { skipCache: true });
expect(restored[sessionKey]).toBeUndefined();
await fs.rm(workspaceDir, { recursive: true, force: true });
}); });
}); });