import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; // Mock the db module vi.mock("../services/db.js", () => ({ cleanupStaleData: vi.fn().mockResolvedValue({ expiredVerifications: 0, orphanedUsage: 0 }), })); vi.mock("../services/logger.js", () => ({ default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), }, })); import { startPeriodicCleanup, stopPeriodicCleanup, CLEANUP_INTERVAL_MS, } from "../utils/periodic-cleanup.js"; import { cleanupStaleData } from "../services/db.js"; import logger from "../services/logger.js"; const mockCleanupStaleData = vi.mocked(cleanupStaleData); describe("periodic-cleanup", () => { beforeEach(() => { vi.useFakeTimers(); vi.clearAllMocks(); stopPeriodicCleanup(); }); afterEach(() => { stopPeriodicCleanup(); vi.useRealTimers(); }); it("should export a 6-hour cleanup interval constant", () => { expect(CLEANUP_INTERVAL_MS).toBe(6 * 60 * 60 * 1000); }); it("should call cleanupStaleData after one interval elapses", async () => { startPeriodicCleanup(); expect(mockCleanupStaleData).not.toHaveBeenCalled(); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); expect(mockCleanupStaleData).toHaveBeenCalledTimes(1); }); it("should call cleanupStaleData multiple times over multiple intervals", async () => { startPeriodicCleanup(); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); expect(mockCleanupStaleData).toHaveBeenCalledTimes(2); }); it("should log errors but not throw when cleanupStaleData fails", async () => { mockCleanupStaleData.mockRejectedValueOnce(new Error("DB connection lost")); startPeriodicCleanup(); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); expect(logger.error).toHaveBeenCalledWith( expect.objectContaining({ err: expect.any(Error) }), expect.stringContaining("Periodic database cleanup failed") ); // Next interval should still work mockCleanupStaleData.mockResolvedValueOnce({ expiredVerifications: 1, orphanedUsage: 0 }); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); expect(mockCleanupStaleData).toHaveBeenCalledTimes(2); }); it("should stop cleanup when stopPeriodicCleanup is called", async () => { startPeriodicCleanup(); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); expect(mockCleanupStaleData).toHaveBeenCalledTimes(1); stopPeriodicCleanup(); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); expect(mockCleanupStaleData).toHaveBeenCalledTimes(1); }); it("should be idempotent — calling startPeriodicCleanup twice doesn't create two intervals", async () => { startPeriodicCleanup(); startPeriodicCleanup(); await vi.advanceTimersByTimeAsync(CLEANUP_INTERVAL_MS); expect(mockCleanupStaleData).toHaveBeenCalledTimes(1); }); });