mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 03:42:21 +03:00
* test(infra): retry recursive temp-dir removal on main (main twin of #11968)
`main` has been red since b342c1a361 on the vitest and integration gates:
✖ tests/unit/autoCombo/provider-family-combos.test.ts > auto/<family>
✖ chat pipeline applies Codex OAuth fingerprint and priority tier inside combos
Both call resetStorage() from beforeEach, which does an fs.rmSync(TEST_DATA_DIR,
{recursive: true, force: true}) with no retry, and intermittently loses the race
with a not-yet-released SQLite handle (ENOTEMPTY).
release/v3.8.51 fixed this in #11968 with a mechanical codemod adding
maxRetries/retryDelay to every recursive rm/rmSync/rmdirSync under tests/, but
that PR landed only on the release branch. Because main only receives work at
the release squash, it stayed broken for the whole cycle — and repo-wide gates
then turn every open PR into main red on checks unrelated to their diff.
This is the --base main twin: re-runs the same codemod that already shipped on
the release branch (scripts/ad-hoc/codemod-rm-maxretries.mjs), so the two
branches converge on identical test-teardown semantics. Test-only; no product
logic is touched.
The remaining three failures reported on #12133 (unit full suite exceeding its
4800s ceiling, package-artifact exceeding 1200s, and the boot-smoke that is
skipped as a consequence) are runner-contention timeouts, not code defects —
validate-release-green.mjs runs those heavy gates concurrently on one shared
hosted runner. There is no fix to port for those.
* chore(scripts): carry the rm-maxretries codemod onto main alongside its output
The codemod that generated the previous commit lives in the repo on
release/v3.8.51 (added by #11968) but was never on main. Bringing it over keeps
the tool next to the change it produced, so the transformation stays
reproducible and auditable from either branch.
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 });
|
|
});
|