// --------------------------------------------------------------------------- // Ordner: lib // Datei: storage.test.js // Relativer Pfad: lib/storage.test.js // --------------------------------------------------------------------------- // tests/lib/storage.test.js import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi, } from "vitest"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { listBranches, listYears, listMonths, listDays, listFiles, __clearStorageCacheForTests, } from "@/lib/storage"; let tmpRoot; beforeAll(async () => { // Create a unique temporary directory for the tests tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "storage-test-")); // Point NAS_ROOT_PATH to our temp directory process.env.NAS_ROOT_PATH = tmpRoot; // Create a fake directory tree: // tmpRoot/NL01/2024/10/23/test.pdf await fs.mkdir(path.join(tmpRoot, "NL01", "2024", "10", "23"), { recursive: true, }); await fs.writeFile( path.join(tmpRoot, "NL01", "2024", "10", "23", "test.pdf"), "dummy-pdf-content" ); // Add snapshot folder which should be ignored await fs.mkdir(path.join(tmpRoot, "@Recently-Snapshot")); }); afterAll(async () => { // Clean up the temporary directory after tests await fs.rm(tmpRoot, { recursive: true, force: true }); }); beforeEach(() => { // RHL-006: // Storage functions now use a TTL cache. We clear it between tests to ensure // each test sees filesystem changes deterministically. __clearStorageCacheForTests(); }); afterEach(() => { // Safety: if a test enables fake timers, always restore real timers afterwards // to avoid affecting unrelated tests. vi.useRealTimers(); }); describe("storage: listBranches", () => { it("returns branch names and filters snapshots", async () => { const branches = await listBranches(); expect(branches).toEqual(["NL01"]); }); }); describe("storage: year/month/day helpers", () => { it("returns years for a branch", async () => { const years = await listYears("NL01"); expect(years).toEqual(["2024"]); }); it("returns months for a branch/year", async () => { const months = await listMonths("NL01", "2024"); expect(months).toEqual(["10"]); }); it("returns days for a branch/year/month", async () => { const days = await listDays("NL01", "2024", "10"); expect(days).toEqual(["23"]); }); }); describe("storage: listFiles", () => { it("returns PDF files with relativePath", async () => { const files = await listFiles("NL01", "2024", "10", "23"); expect(files).toEqual([ { name: "test.pdf", relativePath: "NL01/2024/10/23/test.pdf", }, ]); }); }); describe("storage: micro-cache (RHL-006)", () => { it("keeps results stable within TTL and refreshes after TTL expires", async () => { // Use a dedicated folder so this test doesn't interfere with other fixtures. const dayDir = path.join(tmpRoot, "NL01", "2024", "10", "24"); await fs.mkdir(dayDir, { recursive: true }); // Initial file await fs.writeFile(path.join(dayDir, "a.pdf"), "a"); // Fake timers allow us to advance time without real waiting. vi.useFakeTimers(); const t0 = new Date("2025-01-01T00:00:00.000Z"); vi.setSystemTime(t0); // First call populates the cache (TTL for files: 15s) const first = await listFiles("NL01", "2024", "10", "24"); expect(first.map((f) => f.name)).toEqual(["a.pdf"]); // Add a new file while the cache is still valid. await fs.writeFile(path.join(dayDir, "b.pdf"), "b"); // Second call should still return the cached result (b.pdf not visible yet). const second = await listFiles("NL01", "2024", "10", "24"); expect(second.map((f) => f.name)).toEqual(["a.pdf"]); // Advance time beyond the TTL (15s) so the next call re-reads the directory. vi.setSystemTime(t0.getTime() + 16_000); const third = await listFiles("NL01", "2024", "10", "24"); expect(third.map((f) => f.name)).toEqual(["a.pdf", "b.pdf"]); }); });