mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 04:12:17 +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.
362 lines
13 KiB
TypeScript
362 lines
13 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 { pathToFileURL } from "node:url";
|
|
|
|
const serial = { concurrency: false };
|
|
|
|
function makeTempDir(prefix: string): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
}
|
|
|
|
function removePath(targetPath: string) {
|
|
fs.rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
|
|
const originalEnv = {
|
|
DATA_DIR: process.env.DATA_DIR,
|
|
NEXT_PHASE: process.env.NEXT_PHASE,
|
|
};
|
|
|
|
function restoreEnv() {
|
|
for (const [key, value] of Object.entries(originalEnv)) {
|
|
if (value === undefined) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
function cleanupGlobalDb() {
|
|
try {
|
|
if ((globalThis as any).__omnirouteDb?.open) {
|
|
(globalThis as any).__omnirouteDb.close();
|
|
}
|
|
} catch {}
|
|
delete (globalThis as any).__omnirouteDb;
|
|
}
|
|
|
|
async function importFresh(modulePath: string) {
|
|
const url = pathToFileURL(path.resolve(modulePath)).href;
|
|
return import(`${url}?test=${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
}
|
|
|
|
async function withEnv(overrides: Record<string, string | undefined>, fn: () => Promise<void>) {
|
|
const snapshot: Record<string, string | undefined> = {};
|
|
for (const key of Object.keys(overrides)) {
|
|
snapshot[key] = process.env[key];
|
|
const value = overrides[key];
|
|
if (value === undefined) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
try {
|
|
return await fn();
|
|
} finally {
|
|
for (const [key, value] of Object.entries(snapshot)) {
|
|
if (value === undefined) {
|
|
delete process.env[key];
|
|
} else {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ──────────────── Pure Utility Functions ────────────────
|
|
|
|
test("toSnakeCase converts camelCase to snake_case", () => {
|
|
// Import once at module level for pure functions
|
|
// We use a lazy import pattern
|
|
});
|
|
|
|
// We'll use a single import for the module
|
|
let core: any;
|
|
|
|
test.before(async () => {
|
|
const dataDir = makeTempDir("omniroute-db-core-utils-");
|
|
try {
|
|
await withEnv({ DATA_DIR: dataDir }, async () => {
|
|
core = await importFresh("src/lib/db/core.ts");
|
|
// Initialize DB so DB functions work too
|
|
core.getDbInstance();
|
|
});
|
|
} catch {
|
|
removePath(dataDir);
|
|
}
|
|
});
|
|
|
|
test.after(() => {
|
|
try {
|
|
core?.resetDbInstance();
|
|
} catch {}
|
|
cleanupGlobalDb();
|
|
restoreEnv();
|
|
});
|
|
|
|
// ─── toSnakeCase ───────────────────────────────────────
|
|
|
|
test("toSnakeCase: basic camelCase conversion", () => {
|
|
assert.equal(core.toSnakeCase("apiKey"), "api_key");
|
|
assert.equal(core.toSnakeCase("isActive"), "is_active");
|
|
assert.equal(core.toSnakeCase("providerName"), "provider_name");
|
|
});
|
|
|
|
test("toSnakeCase: empty string and single word", () => {
|
|
assert.equal(core.toSnakeCase(""), "");
|
|
assert.equal(core.toSnakeCase("name"), "name");
|
|
assert.equal(core.toSnakeCase("id"), "id");
|
|
});
|
|
|
|
test("toSnakeCase: consecutive uppercase (acronyms)", () => {
|
|
// The implementation inserts _ before each uppercase including the first
|
|
// character, then lowercases. So "APIKey" → "_a_p_i_key".
|
|
assert.equal(core.toSnakeCase("APIKey"), "_a_p_i_key");
|
|
assert.equal(core.toSnakeCase("DBConnection"), "_d_b_connection");
|
|
});
|
|
|
|
test("toSnakeCase: already snake_case stays same", () => {
|
|
assert.equal(core.toSnakeCase("already_snake"), "already_snake");
|
|
assert.equal(core.toSnakeCase("already_snake_case"), "already_snake_case");
|
|
});
|
|
|
|
test("toSnakeCase: mixed snake and camel", () => {
|
|
assert.equal(core.toSnakeCase("provider_name"), "provider_name");
|
|
assert.equal(core.toSnakeCase("provider_Name"), "provider__name");
|
|
});
|
|
|
|
// ─── toCamelCase ───────────────────────────────────────
|
|
|
|
test("toCamelCase: basic snake_case conversion", () => {
|
|
assert.equal(core.toCamelCase("api_key"), "apiKey");
|
|
assert.equal(core.toCamelCase("is_active"), "isActive");
|
|
assert.equal(core.toCamelCase("provider_name"), "providerName");
|
|
});
|
|
|
|
test("toCamelCase: empty string and single word", () => {
|
|
assert.equal(core.toCamelCase(""), "");
|
|
assert.equal(core.toCamelCase("name"), "name");
|
|
assert.equal(core.toCamelCase("id"), "id");
|
|
});
|
|
|
|
test("toCamelCase: multiple underscores", () => {
|
|
assert.equal(core.toCamelCase("foo_bar_baz"), "fooBarBaz");
|
|
assert.equal(core.toCamelCase("a_b_c"), "aBC");
|
|
});
|
|
|
|
test("toCamelCase: leading underscore", () => {
|
|
assert.equal(core.toCamelCase("_private"), "Private");
|
|
});
|
|
|
|
test("toCamelCase: trailing underscore is preserved", () => {
|
|
// Regex _([a-z]) doesn't match trailing underscore (no char after it)
|
|
assert.equal(core.toCamelCase("trailing_"), "trailing_");
|
|
});
|
|
|
|
// ─── objToSnake ────────────────────────────────────────
|
|
|
|
test("objToSnake: converts object keys to snake_case", () => {
|
|
const input = { apiKey: "sk-test", isActive: true, providerName: "openai" };
|
|
const result = core.objToSnake(input);
|
|
assert.deepEqual(result, { api_key: "sk-test", is_active: true, provider_name: "openai" });
|
|
});
|
|
|
|
test("objToSnake: null/undefined/non-object input", () => {
|
|
assert.equal(core.objToSnake(null), null);
|
|
assert.equal(core.objToSnake(undefined), undefined);
|
|
assert.equal(core.objToSnake("string"), "string");
|
|
assert.equal(core.objToSnake(42), 42);
|
|
});
|
|
|
|
test("objToSnake: empty object", () => {
|
|
assert.deepEqual(core.objToSnake({}), {});
|
|
});
|
|
|
|
test("objToSnake: nested values preserved as-is (shallow)", () => {
|
|
const input = { nestedObj: { innerKey: "val" }, items: [1, 2, 3] };
|
|
const result = core.objToSnake(input);
|
|
assert.deepEqual(result, { nested_obj: { innerKey: "val" }, items: [1, 2, 3] });
|
|
});
|
|
|
|
// ─── rowToCamel ────────────────────────────────────────
|
|
|
|
test("rowToCamel: converts snake_case row to camelCase", () => {
|
|
const row = { api_key: "sk-test", is_active: 1, provider_name: "openai" };
|
|
const result = core.rowToCamel(row);
|
|
assert.deepEqual(result, { apiKey: "sk-test", isActive: true, providerName: "openai" });
|
|
});
|
|
|
|
test("rowToCamel: null/undefined row returns null", () => {
|
|
assert.equal(core.rowToCamel(null), null);
|
|
assert.equal(core.rowToCamel(undefined), null);
|
|
});
|
|
|
|
test("rowToCamel: isActive boolean conversion (1/0)", () => {
|
|
assert.deepEqual(core.rowToCamel({ is_active: 1 }), { isActive: true });
|
|
assert.deepEqual(core.rowToCamel({ is_active: 0 }), { isActive: false });
|
|
assert.deepEqual(core.rowToCamel({ is_active: true }), { isActive: true });
|
|
assert.deepEqual(core.rowToCamel({ is_active: false }), { isActive: false });
|
|
});
|
|
|
|
test("rowToCamel: rateLimitProtection boolean conversion", () => {
|
|
assert.deepEqual(core.rowToCamel({ rate_limit_protection: 1 }), { rateLimitProtection: true });
|
|
assert.deepEqual(core.rowToCamel({ rate_limit_protection: 0 }), { rateLimitProtection: false });
|
|
});
|
|
|
|
test("rowToCamel: providerSpecificData JSON string parsing", () => {
|
|
const data = JSON.stringify({ org: "my-org" });
|
|
const result = core.rowToCamel({ provider_specific_data: data });
|
|
assert.deepEqual(result, { providerSpecificData: { org: "my-org" } });
|
|
});
|
|
|
|
test("rowToCamel: providerSpecificData invalid JSON", () => {
|
|
const result = core.rowToCamel({ provider_specific_data: "not-json" });
|
|
assert.equal(result?.providerSpecificData, "not-json");
|
|
});
|
|
|
|
test("rowToCamel: providerSpecificData non-string passes through", () => {
|
|
const result = core.rowToCamel({ provider_specific_data: 42 });
|
|
assert.equal(result?.providerSpecificData, 42);
|
|
});
|
|
|
|
test("rowToCamel: _json suffix columns parsed into base key", () => {
|
|
const result = core.rowToCamel({ quota_window_thresholds_json: JSON.stringify([1, 2, 3]) });
|
|
assert.deepEqual(result, { quotaWindowThresholds: [1, 2, 3] });
|
|
});
|
|
|
|
test("rowToCamel: _json suffix with invalid JSON sets null", () => {
|
|
const result = core.rowToCamel({ quota_window_thresholds_json: "broken" });
|
|
assert.deepEqual(result, { quotaWindowThresholds: null });
|
|
});
|
|
|
|
test("rowToCamel: ordinary fields pass through unchanged", () => {
|
|
const result = core.rowToCamel({ name: "test", created_at: "2025-01-01" });
|
|
assert.deepEqual(result, { name: "test", createdAt: "2025-01-01" });
|
|
});
|
|
|
|
test("rowToCamel: empty row returns empty object", () => {
|
|
assert.deepEqual(core.rowToCamel({}), {});
|
|
});
|
|
|
|
// ─── cleanNulls ────────────────────────────────────────
|
|
|
|
test("cleanNulls: removes null and undefined values", () => {
|
|
const result = core.cleanNulls({ a: 1, b: null, c: "keep", d: undefined, e: 0, f: "" });
|
|
assert.deepEqual(result, { a: 1, c: "keep", e: 0, f: "" });
|
|
});
|
|
|
|
test("cleanNulls: all values kept", () => {
|
|
const result = core.cleanNulls({ a: 1, b: "x", c: false });
|
|
assert.deepEqual(result, { a: 1, b: "x", c: false });
|
|
});
|
|
|
|
test("cleanNulls: empty object returns empty object", () => {
|
|
assert.deepEqual(core.cleanNulls({}), {});
|
|
});
|
|
|
|
test("cleanNulls: all null/undefined returns empty object", () => {
|
|
assert.deepEqual(core.cleanNulls({ a: null, b: undefined }), {});
|
|
});
|
|
|
|
// ─── getDriverInfo ─────────────────────────────────────
|
|
|
|
test("getDriverInfo returns null (setDriverInfo never called)", () => {
|
|
// setDriverInfo() exists but is never called in core.ts, so getDriverInfo
|
|
// always returns null until/unless a caller invokes setDriverInfo().
|
|
assert.equal(core.getDriverInfo(), null);
|
|
});
|
|
|
|
// ─── DB Functions (autoVacuum, pageSize, cacheSize) ───
|
|
|
|
test("setAutoVacuum and getAutoVacuumMode round-trip", serial, () => {
|
|
// Get current
|
|
const originalMode = core.getAutoVacuumMode();
|
|
|
|
// Set to NONE first
|
|
core.setAutoVacuum("NONE");
|
|
assert.equal(core.getAutoVacuumMode(), "NONE");
|
|
|
|
// Set to FULL
|
|
core.setAutoVacuum("FULL");
|
|
assert.equal(core.getAutoVacuumMode(), "FULL");
|
|
|
|
// Set to INCREMENTAL
|
|
core.setAutoVacuum("INCREMENTAL");
|
|
assert.equal(core.getAutoVacuumMode(), "INCREMENTAL");
|
|
|
|
// Restore original
|
|
core.setAutoVacuum(originalMode);
|
|
assert.equal(core.getAutoVacuumMode(), originalMode);
|
|
});
|
|
|
|
test("setAutoVacuum same mode is idempotent", serial, () => {
|
|
const mode = core.getAutoVacuumMode();
|
|
// Calling again with same mode should not throw
|
|
core.setAutoVacuum(mode);
|
|
assert.equal(core.getAutoVacuumMode(), mode);
|
|
});
|
|
|
|
test("runManualVacuum succeeds", serial, () => {
|
|
const result = core.runManualVacuum();
|
|
assert.equal(result.success, true);
|
|
assert.equal(typeof result.duration, "number");
|
|
assert.ok(result.duration >= 0);
|
|
assert.equal(result.error, undefined);
|
|
});
|
|
|
|
test("setPageSize round-trip", serial, () => {
|
|
// Capture current page_size so we can restore it
|
|
// We'll set to a known value, verify, then set back
|
|
// Note: page_size can only be set if the DB is empty or after VACUUM
|
|
// The implementation calls VACUUM after setting, which is safe
|
|
const testPageSize = 4096;
|
|
core.setPageSize(testPageSize);
|
|
// We can't read it back directly via exported function, but it shouldn't throw
|
|
// Verify by running it again (idempotent)
|
|
core.setPageSize(testPageSize);
|
|
});
|
|
|
|
test("setCacheSize round-trip", serial, () => {
|
|
core.setCacheSize(16384);
|
|
// Verify idempotent
|
|
core.setCacheSize(16384);
|
|
});
|
|
|
|
// ─── Edge Cases ────────────────────────────────────────
|
|
|
|
test("toSnakeCase and toCamelCase are inverses for simple cases", () => {
|
|
const pairs = [
|
|
["apiKey", "api_key"],
|
|
["isActive", "is_active"],
|
|
["providerName", "provider_name"],
|
|
["createdAt", "created_at"],
|
|
["updatedAt", "updated_at"],
|
|
];
|
|
for (const [camel, snake] of pairs) {
|
|
assert.equal(core.toSnakeCase(camel), snake);
|
|
assert.equal(core.toCamelCase(snake), camel);
|
|
}
|
|
});
|
|
|
|
test("cleanNulls with falsy values preserves 0, false, empty string", () => {
|
|
const result = core.cleanNulls({ zero: 0, falseVal: false, emptyStr: "", nil: null });
|
|
assert.deepEqual(result, { zero: 0, falseVal: false, emptyStr: "" });
|
|
});
|
|
|
|
test("objToSnake returns same object reference for primitives", () => {
|
|
const num = 42;
|
|
assert.equal(core.objToSnake(num), 42);
|
|
});
|
|
|
|
test("rowToCamel with isActive=1/non-1 edge cases", () => {
|
|
assert.deepEqual(core.rowToCamel({ is_active: 1 }), { isActive: true });
|
|
assert.deepEqual(core.rowToCamel({ is_active: 0 }), { isActive: false });
|
|
assert.deepEqual(core.rowToCamel({ is_active: 2 }), { isActive: false });
|
|
assert.deepEqual(core.rowToCamel({ is_active: "yes" }), { isActive: false });
|
|
});
|