mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +03:00
Two shards on release/v3.8.51 went red in one day with the same signature —
"ENOTEMPTY, Directory not empty: /tmp/omniroute-<test>-XXXXXX" — from
combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only
.github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass
alone and on re-run: the cleanup races something still writing into the directory
(SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner
the window opens. 1154 test files do their own cleanup with
fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries.
One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record):
every rm / rmSync / rmdirSync option object with `recursive: true` and no
`maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries
ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292
files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included.
Only the option object changes: no call site, assertion or import is touched.
Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292
files; a random 20-file sample runs green (quota-redis-store hangs identically on
the untouched tree — it needs a Redis on localhost, an environment matter). The
four unit shards on this PR are the full run.
126 lines
4.4 KiB
TypeScript
126 lines
4.4 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
import {
|
|
resolveWritableDataDir,
|
|
getDefaultDataDir,
|
|
resolveDataDir,
|
|
} from "../../src/lib/dataPaths.ts";
|
|
|
|
// Running as root bypasses POSIX permission bits, so a chmod-based "unwritable"
|
|
// directory would still be writable and the EACCES/EPERM branch never triggers.
|
|
const IS_ROOT = typeof process.getuid === "function" && process.getuid() === 0;
|
|
const IS_WINDOWS = process.platform === "win32";
|
|
|
|
async function withTempEnv(fn: (paths: { root: string; home: string }) => void | Promise<void>) {
|
|
const originalEnv = { ...process.env };
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "omni-datadir-"));
|
|
const home = path.join(root, "home");
|
|
fs.mkdirSync(home, { recursive: true });
|
|
|
|
delete process.env.DATA_DIR;
|
|
delete process.env.XDG_CONFIG_HOME;
|
|
delete process.env.APPDATA;
|
|
process.env.HOME = home;
|
|
process.env.USERPROFILE = home;
|
|
|
|
try {
|
|
await fn({ root, home });
|
|
} finally {
|
|
for (const key of Object.keys(process.env)) {
|
|
if (!(key in originalEnv)) delete process.env[key];
|
|
}
|
|
for (const [key, value] of Object.entries(originalEnv)) {
|
|
process.env[key] = value;
|
|
}
|
|
// Restore perms before cleanup so rmSync can delete read-only parents.
|
|
try {
|
|
fs.chmodSync(root, 0o755);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
fs.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
}
|
|
|
|
test("resolveWritableDataDir returns the configured DATA_DIR when it is writable", async () => {
|
|
await withTempEnv(({ root }) => {
|
|
const configured = path.join(root, "writable-data");
|
|
process.env.DATA_DIR = configured;
|
|
|
|
const resolved = resolveWritableDataDir();
|
|
assert.equal(resolved, path.resolve(configured));
|
|
// The probe creates the directory as a side effect.
|
|
assert.ok(fs.existsSync(configured));
|
|
});
|
|
});
|
|
|
|
test(
|
|
"resolveWritableDataDir falls back to the default dir when DATA_DIR is not writable (EACCES/EPERM)",
|
|
{ skip: IS_ROOT || IS_WINDOWS },
|
|
async () => {
|
|
await withTempEnv(({ root, home }) => {
|
|
// A read-only parent makes mkdir of the child fail with EACCES/EPERM.
|
|
const lockedParent = path.join(root, "locked");
|
|
fs.mkdirSync(lockedParent, { recursive: true });
|
|
fs.chmodSync(lockedParent, 0o555);
|
|
|
|
const configured = path.join(lockedParent, "data");
|
|
process.env.DATA_DIR = configured;
|
|
|
|
const resolved = resolveWritableDataDir();
|
|
const expectedFallback = getDefaultDataDir();
|
|
|
|
// It must NOT return the unwritable configured dir...
|
|
assert.notEqual(resolved, path.resolve(configured));
|
|
// ...and instead fall back to the default user dir (~/.omniroute under HOME).
|
|
assert.equal(resolved, expectedFallback);
|
|
assert.ok(resolved.startsWith(path.resolve(home)));
|
|
});
|
|
}
|
|
);
|
|
|
|
test("resolveWritableDataDir returns the default dir (no probe) when DATA_DIR is unset", async () => {
|
|
await withTempEnv(() => {
|
|
delete process.env.DATA_DIR;
|
|
// #10428: this asserts the SERVER path. Since the test-context guard now redirects a
|
|
// DATA_DIR-less test process to a temp dir (so a test can never open the operator's
|
|
// real DB), opt back in explicitly here — otherwise this test would be asserting the
|
|
// guard's behavior instead of the server's.
|
|
process.env.OMNIROUTE_ALLOW_DEFAULT_DATA_DIR = "1";
|
|
const resolved = resolveWritableDataDir();
|
|
assert.equal(resolved, getDefaultDataDir());
|
|
// Matches the pure resolver when no override is present.
|
|
assert.equal(resolved, resolveDataDir());
|
|
});
|
|
});
|
|
|
|
test("resolveWritableDataDir rethrows non-permission errors", { skip: IS_WINDOWS }, async () => {
|
|
await withTempEnv(({ root }) => {
|
|
// Point DATA_DIR at a path whose parent is a regular file → ENOTDIR, not EACCES.
|
|
const fileParent = path.join(root, "iam-a-file");
|
|
fs.writeFileSync(fileParent, "x");
|
|
|
|
const configured = path.join(fileParent, "data");
|
|
process.env.DATA_DIR = configured;
|
|
|
|
assert.throws(
|
|
() => resolveWritableDataDir(),
|
|
(err: NodeJS.ErrnoException) => {
|
|
return err.code !== "EACCES" && err.code !== "EPERM";
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
test("resolveWritableDataDir leaves the cloud sentinel untouched", async () => {
|
|
await withTempEnv(() => {
|
|
process.env.DATA_DIR = "/some/configured/path";
|
|
const resolved = resolveWritableDataDir({ isCloud: true });
|
|
assert.equal(resolved, "/tmp");
|
|
});
|
|
});
|