feat(FN-4743): complete Step 3 — wire mail cleanup into maintenance

Fusion-Task-Id: FN-4743
Fusion-Task-Lineage: 50d1d8e4-66d5-4299-843b-28072a904277
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 10:25:11 -07:00
committed by gsxdsm
parent 229f3a9467
commit 96be8d4cd7
3 changed files with 112 additions and 1 deletions

View File

@@ -0,0 +1,92 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { rm } from "node:fs/promises";
import { Database, MessageStore } from "@fusion/core";
import { SelfHealingManager } from "../self-healing.js";
describe("FN-4743: self-healing mail cleanup maintenance", () => {
let tmpRoot: string;
let db: Database;
let messageStore: MessageStore;
beforeAll(() => {
tmpRoot = mkdtempSync(join(tmpdir(), "fusion-self-healing-mail-cleanup-"));
const fusionDir = join(tmpRoot, ".fusion");
db = new Database(fusionDir, { inMemory: true });
db.init();
messageStore = new MessageStore(db);
});
beforeEach(() => {
db.exec("DELETE FROM messages");
});
afterAll(async () => {
db.close();
await rm(tmpRoot, { recursive: true, force: true });
});
function buildManager(mailAutoCleanupDays?: number, includeMessageStore = true) {
const store = {
getSettings: vi.fn(async () => ({ maintenanceIntervalMs: 0, globalPause: false, enginePaused: false, mailAutoCleanupDays })),
} as any;
const manager = new SelfHealingManager(store, {
rootDir: tmpRoot,
messageStore: includeMessageStore ? messageStore : undefined,
});
vi.spyOn(manager as any, "pruneWorktrees").mockResolvedValue(undefined);
vi.spyOn(manager as any, "cleanupOrphans").mockResolvedValue(undefined);
vi.spyOn(manager as any, "cleanupOrphanedBranches").mockResolvedValue(undefined);
vi.spyOn(manager as any, "checkpointWal").mockReturnValue(undefined);
vi.spyOn(manager as any, "enforceWorktreeCap").mockResolvedValue(undefined);
vi.spyOn(manager, "archiveStaleDoneTasks").mockResolvedValue(0);
return manager;
}
it("removes only stale messages when mailAutoCleanupDays is enabled", async () => {
const stale = messageStore.sendMessage({ fromId: "user-1", fromType: "user", toId: "agent-1", toType: "agent", content: "stale", type: "user-to-agent" });
const fresh = messageStore.sendMessage({ fromId: "agent-1", fromType: "agent", toId: "user-1", toType: "user", content: "fresh", type: "agent-to-user" });
const staleTimestamp = new Date(Date.now() - 12 * 86_400_000).toISOString();
const freshTimestamp = new Date(Date.now() - 1 * 86_400_000).toISOString();
db.prepare("UPDATE messages SET updatedAt = ? WHERE id = ?").run(staleTimestamp, stale.id);
db.prepare("UPDATE messages SET updatedAt = ? WHERE id = ?").run(freshTimestamp, fresh.id);
const manager = buildManager(7, true);
await (manager as any).runMaintenance();
expect(messageStore.getMessage(stale.id)).toBeNull();
expect(messageStore.getMessage(fresh.id)).not.toBeNull();
});
it("is a no-op when mailAutoCleanupDays is off or undefined", async () => {
const oldMessage = messageStore.sendMessage({ fromId: "user-1", fromType: "user", toId: "agent-1", toType: "agent", content: "keep", type: "user-to-agent" });
const oldTimestamp = new Date(Date.now() - 100 * 86_400_000).toISOString();
db.prepare("UPDATE messages SET updatedAt = ? WHERE id = ?").run(oldTimestamp, oldMessage.id);
const managerOff = buildManager(0, true);
await (managerOff as any).runMaintenance();
expect(messageStore.getMessage(oldMessage.id)).not.toBeNull();
const managerUndefined = buildManager(undefined, true);
await (managerUndefined as any).runMaintenance();
expect(messageStore.getMessage(oldMessage.id)).not.toBeNull();
});
it("is a no-op when messageStore option is omitted", async () => {
const oldMessage = messageStore.sendMessage({ fromId: "user-2", fromType: "user", toId: "agent-2", toType: "agent", content: "keep-no-store", type: "user-to-agent" });
const oldTimestamp = new Date(Date.now() - 100 * 86_400_000).toISOString();
db.prepare("UPDATE messages SET updatedAt = ? WHERE id = ?").run(oldTimestamp, oldMessage.id);
const manager = buildManager(7, false);
await (manager as any).runMaintenance();
expect(messageStore.getMessage(oldMessage.id)).not.toBeNull();
});
});