import { describe, expect, it, vi, beforeEach } from "vitest"; import type { IStorageEventWatchHandlers } from "@lib/managers/adapters"; import type { NodeFile } from "../adapters/NodeTypes"; // ── chokidar mock ────────────────────────────────────────────────────────────── // Must be hoisted before imports that pull in chokidar. const mockWatcher = { on: vi.fn().mockReturnThis(), once: vi.fn((event: string, cb: () => void) => { if (event === "ready") cb(); return mockWatcher; }), close: vi.fn(() => Promise.resolve()), }; vi.mock("chokidar", () => ({ watch: vi.fn(() => mockWatcher), })); import * as chokidar from "chokidar"; import { CLIStorageEventManagerAdapter } from "./CLIStorageEventManagerAdapter"; // ── helpers ─────────────────────────────────────────────────────────────────── function makeHandlers(): IStorageEventWatchHandlers { return { onCreate: vi.fn(), onChange: vi.fn(), onDelete: vi.fn(), onRename: vi.fn(), } as any; } // ── tests ───────────────────────────────────────────────────────────────────── describe("CLIStorageEventManagerAdapter", () => { beforeEach(() => { vi.clearAllMocks(); // Restore the default once() behaviour (ready fires synchronously). mockWatcher.once.mockImplementation((event: string, cb: () => void) => { if (event === "ready") cb(); return mockWatcher; }); }); it("beginWatch is no-op when watchEnabled=false", async () => { const adapter = new CLIStorageEventManagerAdapter("/base", undefined, false); const handlers = makeHandlers(); await adapter.watch.beginWatch(handlers); expect(chokidar.watch).not.toHaveBeenCalled(); }); it("beginWatch calls chokidar.watch when watchEnabled=true", async () => { const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true); const handlers = makeHandlers(); await adapter.watch.beginWatch(handlers); expect(chokidar.watch).toHaveBeenCalledTimes(1); expect(chokidar.watch).toHaveBeenCalledWith( "/base", expect.objectContaining({ ignoreInitial: true }) ); }); it("add event produces NodeFile with correct relative path via onCreate", async () => { const basePath = "/vault/base"; const adapter = new CLIStorageEventManagerAdapter(basePath, undefined, true); const handlers = makeHandlers(); await adapter.watch.beginWatch(handlers); // Find the callback registered for the "add" event. const addCall = mockWatcher.on.mock.calls.find(([event]) => event === "add"); expect(addCall).toBeDefined(); const addCallback = addCall![1] as (filePath: string, stats: any) => void; const fakeStats = { ctimeMs: 1000, mtimeMs: 2000, size: 42 }; addCallback(`${basePath}/subdir/note.md`, fakeStats); expect(handlers.onCreate).toHaveBeenCalledTimes(1); const created = (handlers.onCreate as ReturnType).mock.calls[0][0] as NodeFile; expect(created.path).toBe("subdir/note.md"); expect(created.stat?.size).toBe(42); }); it("close() calls watcher.close()", async () => { const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true); const handlers = makeHandlers(); await adapter.watch.beginWatch(handlers); await adapter.close(); expect(mockWatcher.close).toHaveBeenCalledTimes(1); }); it("close() is safe when no watcher was started", async () => { const adapter = new CLIStorageEventManagerAdapter("/base", undefined, false); // Should not throw. await expect(adapter.close()).resolves.toBeUndefined(); expect(mockWatcher.close).not.toHaveBeenCalled(); }); it("error event triggers process.exit(1)", async () => { const adapter = new CLIStorageEventManagerAdapter("/base", undefined, true); const handlers = makeHandlers(); await adapter.watch.beginWatch(handlers); const processExitSpy = vi.spyOn(process, "exit").mockImplementation((() => {}) as any); const errorCall = mockWatcher.on.mock.calls.find(([event]) => event === "error"); expect(errorCall).toBeDefined(); const errorCallback = errorCall![1] as (err: Error) => void; errorCallback(new Error("disk failure")); expect(processExitSpy).toHaveBeenCalledWith(1); processExitSpy.mockRestore(); }); });