Backup directory failures must not crash the whole server
A fresh Windows install reported the server failing around backups. createBackupService created the backups subdirectory once, synchronously, at startup with no error handling - any failure there (permissions, a path beyond Windows' MAX_PATH, a OneDrive/cloud-sync lock, antivirus) crashed the entire Node process before it started listening, taking down timing with it instead of just leaving backups unavailable. Directory creation is now retried lazily and non-fatally on each backup attempt, and a post-backup file rename retries briefly on EBUSY/EPERM since a freshly written file is sometimes momentarily locked by antivirus/cloud-sync scanning on Windows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createRequire } from "node:module";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { chmodSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { tmpdir } from "node:os";
|
||||
@@ -102,6 +102,24 @@ test("local restore validates and replaces a stopped test database with a safety
|
||||
}
|
||||
});
|
||||
|
||||
test("an unwritable backups directory never crashes server startup, and surfaces a clear error on backup instead", async () => {
|
||||
const dataDir = mkdtempSync(join(tmpdir(), "racecore-backup-blocked-"));
|
||||
const db = createTestDb(dataDir);
|
||||
try {
|
||||
chmodSync(dataDir, 0o500); // read/execute only: mkdir("backups") inside it fails with EACCES
|
||||
let service;
|
||||
assert.doesNotThrow(() => {
|
||||
service = createBackupService({ db, dataDir, maxBackups: 3 });
|
||||
}, "constructing the service must not throw even if the backups directory cannot be created");
|
||||
assert.deepEqual(service.listBackups(), [], "listing degrades to empty instead of throwing");
|
||||
await assert.rejects(() => service.createBackup({ reason: "manual" }), /backup directory/i);
|
||||
} finally {
|
||||
chmodSync(dataDir, 0o700);
|
||||
db.close();
|
||||
rmSync(dataDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("backup file segments are bounded and filesystem safe", () => {
|
||||
assert.equal(safeSegment("A final / ../ unsafe"), "a-final-unsafe");
|
||||
assert.equal(safeSegment(""), "backup");
|
||||
|
||||
Reference in New Issue
Block a user