Files
OmniRoute/tests/unit/db-import-ebusy-5406.test.ts
Diego Rodrigues de Sa e Souza 0adae00c7b Release v3.8.42 (#5459)
Release v3.8.42 — full CHANGELOG in CHANGELOG.md.

CI: 103 checks green incl. CodeQL (all languages), Semgrep, all 8 unit shards,
coverage, Node 24 compat, and integration tests. Full unit suite validated
locally: 19437 pass / 0 fail. The 3 red checks are advisory and do not gate
main (no required status checks): SonarCloud/SonarQube new-code coverage gate,
and PR Test Policy (test-masking detector flagging the legitimate dead-Phind
provider removal in #5530 — reviewed, correct).

Includes cycle-close reconciliation + repair of inherited base-red tests from
#5480/#5527/#5427/#5521 that the PR->release fast-path did not exercise.
2026-06-30 06:54:29 -03:00

38 lines
1.7 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
// Regression guard for #5406: the database-import route deleted the live
// storage.sqlite + WAL/-shm/-journal sidecars with a plain synchronous
// `fs.unlinkSync` and no retry. On Windows the OS releases the SQLite file
// handle asynchronously after `db.close()` (mmap / antivirus), so the immediate
// unlink races and throws EBUSY. The restore path already solved this with
// `unlinkFileWithRetry` (EBUSY/EPERM backoff); the import path must use the
// same helper instead of raw `fs.unlinkSync`.
const here = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(here, "..", "..");
const importRoute = join(repoRoot, "src/app/api/db-backups/import/route.ts");
test("#5406: import route uses unlinkFileWithRetry (EBUSY-safe on Windows)", () => {
const src = readFileSync(importRoute, "utf8");
assert.match(
src,
/unlinkFileWithRetry/,
"import route must delete the sqlite files via unlinkFileWithRetry (EBUSY retry)"
);
});
test("#5406: import route does not raw-unlink the live sqlite files (EBUSY race)", () => {
const src = readFileSync(importRoute, "utf8");
// The buggy code deleted the sqlite + WAL sidecars with `fs.unlinkSync(filePath)`
// inside the sqliteFilesToReplace loop. Only that path races to EBUSY; the
// temp-upload cleanup (`fs.unlinkSync(tmpPath)`) is a different, unlocked file.
assert.ok(
!/fs\.unlinkSync\s*\(\s*filePath\b/.test(src),
"the sqlite-replace loop must use unlinkFileWithRetry, not raw fs.unlinkSync(filePath)"
);
});