Files
PaperClipAI/server/src/__tests__/idle-database-backup.test.ts
T
2026-11-03 10:47:19 -06:00

49 lines
2.6 KiB
TypeScript

import { mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { gzipSync } from "node:zlib";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { clearIdleBackupWakeRequired, idleBackupWakeMarker, markIdleBackupWakeRequired, verifyIdleDatabaseBackup } from "../services/idle-database-backup.js";
let directory: string;
beforeEach(async () => { directory = await mkdtemp(path.join(os.tmpdir(), "idle-backup-")); });
afterEach(async () => { await rm(directory, { recursive: true, force: true }); });
describe("idle database backup persistence", () => {
it("keeps a restart marker until a fresh valid backup is complete", async () => {
const archive = path.join(directory, "backup.sql.gz");
await markIdleBackupWakeRequired(directory);
await writeFile(archive, gzipSync("CREATE TABLE fixture (id integer);\n"));
await verifyIdleDatabaseBackup(archive);
// Verification on the sleep path must not remove the wake obligation.
expect(await readFile(idleBackupWakeMarker(directory), "utf8")).toContain("restarts");
await clearIdleBackupWakeRequired(directory);
await expect(readFile(idleBackupWakeMarker(directory))).rejects.toMatchObject({ code: "ENOENT" });
expect(await readFile(archive)).toEqual(gzipSync("CREATE TABLE fixture (id integer);\n"));
});
it.each([
["empty file", Buffer.alloc(0)], ["empty SQL", gzipSync("")],
["truncated gzip", gzipSync("SELECT 1;").subarray(0, -4)],
["plain text", Buffer.from("SELECT 1;")],
])("rejects %s and retains the restart obligation", async (_label, contents) => {
await markIdleBackupWakeRequired(directory);
const archive = path.join(directory, "invalid.sql.gz");
await writeFile(archive, contents);
await expect(verifyIdleDatabaseBackup(archive)).rejects.toThrow();
expect(await readFile(idleBackupWakeMarker(directory), "utf8")).toContain("restarts");
});
it("rejects a missing archive and symlinked archive or marker", async () => {
const archive = path.join(directory, "backup.sql.gz");
await expect(verifyIdleDatabaseBackup(archive)).rejects.toThrow();
const target = path.join(directory, "target");
await writeFile(target, gzipSync("SELECT 1;"));
await symlink(target, archive);
await expect(verifyIdleDatabaseBackup(archive)).rejects.toThrow();
await symlink(target, idleBackupWakeMarker(directory));
await expect(markIdleBackupWakeRequired(directory)).rejects.toThrow();
expect(await readFile(target)).toEqual(gzipSync("SELECT 1;"));
});
});