fix: never replace an unreadable database file with an empty one

This commit is contained in:
2026-11-09 01:28:37 +07:00
parent 6b06059411
commit 97fb59a282
6 changed files with 272 additions and 54 deletions
@@ -0,0 +1,65 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import fs from "fs";
import os from "os";
import path from "path";
const tempDirs: string[] = [];
const originalEnv = { ...process.env };
function makeTempDir(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "termix-db-guard-"));
tempDirs.push(dir);
return dir;
}
beforeEach(() => {
vi.resetModules();
});
afterEach(() => {
process.env = { ...originalEnv };
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
describe("unreadable encrypted database guard", () => {
it("refuses to start and leaves the file untouched", async () => {
const dataDir = makeTempDir();
const encryptedDbPath = path.join(dataDir, "db.sqlite.encrypted");
// A truncated write: the file exists but its header cannot be parsed.
const damaged = Buffer.from("not-a-valid-encrypted-database-header");
fs.writeFileSync(encryptedDbPath, damaged);
process.env.DATA_DIR = dataDir;
process.env.DB_FILE_ENCRYPTION = "true";
process.env.DATABASE_KEY = "a".repeat(64);
delete process.env.ALLOW_UNREADABLE_DB_RESET;
const { initializeDatabase } = await import("../../database/db/index.js");
await expect(initializeDatabase()).rejects.toThrow(
/not a valid encrypted database/,
);
// the damaged file must survive for recovery
expect(fs.readFileSync(encryptedDbPath)).toEqual(damaged);
});
it("names the opt-out environment variable in the error", async () => {
const dataDir = makeTempDir();
fs.writeFileSync(path.join(dataDir, "db.sqlite.encrypted"), "damaged");
process.env.DATA_DIR = dataDir;
process.env.DB_FILE_ENCRYPTION = "true";
process.env.DATABASE_KEY = "a".repeat(64);
delete process.env.ALLOW_UNREADABLE_DB_RESET;
const { initializeDatabase, ALLOW_UNREADABLE_DB_RESET_ENV } =
await import("../../database/db/index.js");
await expect(initializeDatabase()).rejects.toThrow(
new RegExp(ALLOW_UNREADABLE_DB_RESET_ENV),
);
});
});