Verify SQLite backups and restores
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, it } from "node:test";
|
||||
import Database from "better-sqlite3";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import {
|
||||
createVerifiedDatabaseBackup,
|
||||
verifyDatabaseFile,
|
||||
} from "../src/db/database-backup.js";
|
||||
import { createDatabaseContext } from "../src/db/database-context.js";
|
||||
import { projects } from "../src/db/schema/projects.js";
|
||||
|
||||
describe("database backup and restore", () => {
|
||||
it("backs up committed WAL data and restores it as an independent database", async () => {
|
||||
const testDirectory = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), "leistungsbilanz-db-backup-")
|
||||
);
|
||||
const sourcePath = path.join(testDirectory, "source.db");
|
||||
const backupPath = path.join(testDirectory, "backup.db");
|
||||
const restoredPath = path.join(testDirectory, "restored.db");
|
||||
const source = createDatabaseContext(sourcePath);
|
||||
|
||||
try {
|
||||
source.sqlite.pragma("journal_mode = WAL");
|
||||
migrate(source.db, {
|
||||
migrationsFolder: path.resolve("src", "db", "migrations"),
|
||||
});
|
||||
source.db
|
||||
.insert(projects)
|
||||
.values({ id: "project-in-backup", name: "Backup project" })
|
||||
.run();
|
||||
|
||||
assert.equal(fs.existsSync(`${sourcePath}-wal`), true);
|
||||
|
||||
const backupVerification = await createVerifiedDatabaseBackup(
|
||||
sourcePath,
|
||||
backupPath
|
||||
);
|
||||
assert.deepEqual(backupVerification, {
|
||||
integrityCheck: "ok",
|
||||
foreignKeyViolations: 0,
|
||||
});
|
||||
|
||||
source.db
|
||||
.insert(projects)
|
||||
.values({ id: "project-after-backup", name: "Later project" })
|
||||
.run();
|
||||
source.close();
|
||||
|
||||
await createVerifiedDatabaseBackup(backupPath, restoredPath);
|
||||
const restored = new Database(restoredPath, {
|
||||
readonly: true,
|
||||
fileMustExist: true,
|
||||
});
|
||||
try {
|
||||
const restoredProjects = restored
|
||||
.prepare("SELECT id, name FROM projects ORDER BY id")
|
||||
.all();
|
||||
assert.deepEqual(restoredProjects, [
|
||||
{ id: "project-in-backup", name: "Backup project" },
|
||||
]);
|
||||
} finally {
|
||||
restored.close();
|
||||
}
|
||||
} finally {
|
||||
if (source.sqlite.open) {
|
||||
source.close();
|
||||
}
|
||||
fs.rmSync(testDirectory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a corrupt database file", () => {
|
||||
const testDirectory = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), "leistungsbilanz-db-verify-")
|
||||
);
|
||||
const corruptPath = path.join(testDirectory, "corrupt.db");
|
||||
|
||||
try {
|
||||
fs.writeFileSync(corruptPath, "not a sqlite database");
|
||||
assert.throws(() => verifyDatabaseFile(corruptPath), /not a database/);
|
||||
} finally {
|
||||
fs.rmSync(testDirectory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user