import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { MemoryCache } from "@suaveplan/cache"; import { Drive } from "@suaveplan/drive"; import { createNodeFsStorage } from "@suaveplan/storage-file"; import { afterEach, beforeEach, describe, expect, it, } from "@suaveplan/testing/runner"; import { driveCachePlugin } from "../../src/plugin/plugin.js"; describe("drive-plugin-cache integration: MemoryCache", () => { let rootDir: string; let drive: Drive; beforeEach(async () => { rootDir = mkdtempSync(join(tmpdir(), "drive-plugin-cache-mem-")); const fileStorage = createNodeFsStorage({ rootDir, mkdirp: true }); const storage = Object.assign(fileStorage, { id: "default" }); drive = new Drive(storage); await drive.initialize(); }); afterEach(async () => { await drive.dispose(); rmSync(rootDir, { recursive: true, force: true }); }); it("second read of the same path is served from the cache", { timeout: 30000, }, async () => { const cache = new MemoryCache({ maxSize: 100 }); const handle = await drive.usePlugin(driveCachePlugin({ cache })); const payload = new Uint8Array([0x4d, 0x4f, 0x56, 0x21]); await drive.write("clip.mov", payload); const bytes1 = await drive.read("clip.mov"); const bytes2 = await drive.read("clip.mov"); expect(Array.from(bytes1)).toEqual(Array.from(payload)); expect(Array.from(bytes2)).toEqual(Array.from(payload)); const stats = handle.getStats(); expect(stats.hits).toBe(1); expect(stats.misses).toBe(1); expect(stats.populations).toBe(1); }); it("write invalidates a previously populated entry", { timeout: 30000, }, async () => { const cache = new MemoryCache({ maxSize: 100 }); const handle = await drive.usePlugin(driveCachePlugin({ cache })); await drive.write("x.bin", new Uint8Array([1, 2, 3])); await drive.read("x.bin"); // populates await drive.write("x.bin", new Uint8Array([9, 9, 9])); // invalidates const fresh = await drive.read("x.bin"); expect(Array.from(fresh)).toEqual([9, 9, 9]); // Two writes → two post-success invalidations. The first targets an // unpopulated key (idempotent on the cache); the second actually // evicts the populated entry produced by the intermediate read. expect(handle.getStats().invalidations).toBe(2); }); });