Files
Moltbot/src/gateway/boot.test.ts
2026-02-19 15:19:38 +00:00

220 lines
8.2 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { SessionScope } from "../config/sessions/types.js";
const agentCommand = vi.fn();
vi.mock("../commands/agent.js", () => ({ agentCommand }));
const { runBootOnce } = await import("./boot.js");
const { resolveAgentIdFromSessionKey, resolveAgentMainSessionKey, resolveMainSessionKey } =
await import("../config/sessions/main-session.js");
const { resolveStorePath } = await import("../config/sessions/paths.js");
const { loadSessionStore, saveSessionStore } = await import("../config/sessions/store.js");
describe("runBootOnce", () => {
const resolveMainStore = (
cfg: {
session?: { store?: string; scope?: SessionScope; mainKey?: string };
agents?: { list?: Array<{ id?: string; default?: boolean }> };
} = {},
) => {
const sessionKey = resolveMainSessionKey(cfg);
const agentId = resolveAgentIdFromSessionKey(sessionKey);
const storePath = resolveStorePath(cfg.session?.store, { agentId });
return { sessionKey, storePath };
};
beforeEach(async () => {
vi.clearAllMocks();
const { storePath } = resolveMainStore();
await fs.rm(storePath, { force: true });
});
const makeDeps = () => ({
sendMessageWhatsApp: vi.fn(),
sendMessageTelegram: vi.fn(),
sendMessageDiscord: vi.fn(),
sendMessageSlack: vi.fn(),
sendMessageSignal: vi.fn(),
sendMessageIMessage: vi.fn(),
});
const mockAgentUpdatesMainSession = (storePath: string, sessionKey: string) => {
agentCommand.mockImplementation(async (opts: { sessionId?: string }) => {
const current = loadSessionStore(storePath, { skipCache: true });
current[sessionKey] = {
sessionId: String(opts.sessionId),
updatedAt: Date.now(),
};
await saveSessionStore(storePath, current);
});
};
it("skips when BOOT.md is missing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "skipped",
reason: "missing",
});
expect(agentCommand).not.toHaveBeenCalled();
await fs.rm(workspaceDir, { recursive: true, force: true });
});
it.each([
{ title: "empty", content: " \n", reason: "empty" as const },
{ title: "whitespace-only", content: "\n\t ", reason: "empty" as const },
])("skips when BOOT.md is $title", async ({ content, reason }) => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8");
await expect(runBootOnce({ cfg: {}, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "skipped",
reason,
});
expect(agentCommand).not.toHaveBeenCalled();
await fs.rm(workspaceDir, { recursive: true, force: true });
});
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.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8");
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).toEqual(
expect.objectContaining({
deliver: false,
sessionKey: resolveMainSessionKey({}),
}),
);
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 () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
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",
});
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 () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
const content = "Say hello when you wake up.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8");
agentCommand.mockResolvedValue(undefined);
const cfg = {};
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran",
});
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 () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
const content = "Say hello when you wake up.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8");
const cfg = {};
const { sessionKey, storePath } = resolveMainStore(cfg);
const existingSessionId = "main-session-abc123";
await saveSessionStore(storePath, {
[sessionKey]: {
sessionId: existingSessionId,
updatedAt: Date.now(),
},
});
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 () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
const content = "Check if the system is healthy.";
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), content, "utf-8");
const cfg = {};
const { sessionKey, storePath } = resolveMainStore(cfg);
const existingSessionId = "main-session-xyz789";
await saveSessionStore(storePath, {
[sessionKey]: {
sessionId: existingSessionId,
updatedAt: Date.now() - 60_000, // 1 minute ago
},
});
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 () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-boot-"));
await fs.writeFile(path.join(workspaceDir, "BOOT.md"), "health check", "utf-8");
const cfg = {};
const { sessionKey, storePath } = resolveMainStore(cfg);
mockAgentUpdatesMainSession(storePath, sessionKey);
await expect(runBootOnce({ cfg, deps: makeDeps(), workspaceDir })).resolves.toEqual({
status: "ran",
});
const restored = loadSessionStore(storePath, { skipCache: true });
expect(restored[sessionKey]).toBeUndefined();
await fs.rm(workspaceDir, { recursive: true, force: true });
});
});