Files
openclaw/src/gateway/server-maintenance.test.ts
2026-03-07 17:58:31 +00:00

127 lines
3.5 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import type { HealthSummary } from "../commands/health.js";
const cleanOldMediaMock = vi.fn(async () => {});
vi.mock("../media/store.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../media/store.js")>();
return {
...actual,
cleanOldMedia: cleanOldMediaMock,
};
});
const MEDIA_CLEANUP_TTL_MS = 24 * 60 * 60_000;
function createMaintenanceTimerDeps() {
return {
broadcast: () => {},
nodeSendToAllSubscribed: () => {},
getPresenceVersion: () => 1,
getHealthVersion: () => 1,
refreshGatewayHealthSnapshot: async () => ({ ok: true }) as HealthSummary,
logHealth: { error: () => {} },
dedupe: new Map(),
chatAbortControllers: new Map(),
chatRunState: { abortedRuns: new Map() },
chatRunBuffers: new Map(),
chatDeltaSentAt: new Map(),
removeChatRun: () => undefined,
agentRunSeq: new Map(),
nodeSendToSession: () => {},
};
}
function stopMaintenanceTimers(timers: {
tickInterval: NodeJS.Timeout;
healthInterval: NodeJS.Timeout;
dedupeCleanup: NodeJS.Timeout;
mediaCleanup: NodeJS.Timeout | null;
}) {
clearInterval(timers.tickInterval);
clearInterval(timers.healthInterval);
clearInterval(timers.dedupeCleanup);
if (timers.mediaCleanup) {
clearInterval(timers.mediaCleanup);
}
}
describe("startGatewayMaintenanceTimers", () => {
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
});
it("does not schedule recursive media cleanup unless ttl is configured", async () => {
vi.useFakeTimers();
const { startGatewayMaintenanceTimers } = await import("./server-maintenance.js");
const timers = startGatewayMaintenanceTimers({
...createMaintenanceTimerDeps(),
});
expect(cleanOldMediaMock).not.toHaveBeenCalled();
expect(timers.mediaCleanup).toBeNull();
stopMaintenanceTimers(timers);
});
it("runs startup media cleanup and repeats it hourly", async () => {
vi.useFakeTimers();
const { startGatewayMaintenanceTimers } = await import("./server-maintenance.js");
const timers = startGatewayMaintenanceTimers({
...createMaintenanceTimerDeps(),
mediaCleanupTtlMs: MEDIA_CLEANUP_TTL_MS,
});
expect(cleanOldMediaMock).toHaveBeenCalledWith(MEDIA_CLEANUP_TTL_MS, {
recursive: true,
pruneEmptyDirs: true,
});
cleanOldMediaMock.mockClear();
await vi.advanceTimersByTimeAsync(60 * 60_000);
expect(cleanOldMediaMock).toHaveBeenCalledWith(MEDIA_CLEANUP_TTL_MS, {
recursive: true,
pruneEmptyDirs: true,
});
stopMaintenanceTimers(timers);
});
it("skips overlapping media cleanup runs", async () => {
vi.useFakeTimers();
let resolveCleanup = () => {};
let cleanupReady = false;
cleanOldMediaMock.mockImplementation(
() =>
new Promise<void>((resolve) => {
resolveCleanup = resolve;
cleanupReady = true;
}),
);
const { startGatewayMaintenanceTimers } = await import("./server-maintenance.js");
const timers = startGatewayMaintenanceTimers({
...createMaintenanceTimerDeps(),
mediaCleanupTtlMs: MEDIA_CLEANUP_TTL_MS,
});
expect(cleanOldMediaMock).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(60 * 60_000);
expect(cleanOldMediaMock).toHaveBeenCalledTimes(1);
if (cleanupReady) {
resolveCleanup();
}
await Promise.resolve();
await vi.advanceTimersByTimeAsync(60 * 60_000);
expect(cleanOldMediaMock).toHaveBeenCalledTimes(2);
stopMaintenanceTimers(timers);
});
});