All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 18m15s
- Cleans expired verifications and orphaned usage rows - Previously only ran once on startup (13d+ uptime = accumulation) - Interval uses .unref() to not block graceful shutdown - Stopped during shutdown before pool.end() - Idempotent start (safe to call multiple times) - 6 TDD tests added (periodic-cleanup.test.ts) - 663 tests total, all passing
98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
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);
|
|
});
|
|
});
|