Files
OmniRoute/tests/unit/db-providers-split.test.ts
Diego Rodrigues de Sa e Souza c79e8561ff refactor(db): extract columns/nodes/rate-limit leaves from db/providers.ts (#5714)
db/providers.ts was a 1106-line god-file mixing four concerns. Extract the
three acyclic, cohesive slices into sibling leaf modules under
src/lib/db/providers/, leaving the tightly-coupled connection-CRUD core in
the host:

  - providers/columns.ts   (116)  10 pure column-normalizer helpers (DB-free)
  - providers/nodes.ts      (163)  6 provider-node CRUD functions
  - providers/rateLimit.ts  (177)  6 rate-limit/quota runtime helpers + formatResetCountdown

Host providers.ts: 1106 -> 719 lines. The connection-CRUD core does not call
any node or rate-limit function (verified), so the host re-exports the 12
moved public symbols via `export { ... } from './providers/<leaf>'` — the
module's public API stays IDENTICAL (23 symbols). Bodies moved verbatim
(byte-identical); the only edit to a moved line is the added `export` on the
10 previously-private normalizers.

Behavior-preserving: 122 existing provider/quota/rate-limit consumer tests
stay green; new tests/unit/db-providers-split.test.ts guards the re-export
barrel + characterizes the pure column helpers (38 assertions).

Refs #3501 (god-file structural shrink).
2026-06-30 18:32:52 -03:00

175 lines
6.5 KiB
TypeScript

/**
* Characterization + API-surface test: providers.ts god-file decomposition.
*
* The host src/lib/db/providers.ts was split into three sibling leaf modules
* under src/lib/db/providers/:
* - columns.ts — 10 pure column-normalizer helpers (DB-free)
* - nodes.ts — 6 provider-node CRUD functions
* - rateLimit.ts — 6 rate-limit/quota runtime helpers + formatResetCountdown
*
* This test verifies that:
* 1. The pure column helpers in columns.ts behave correctly (DB-free).
* 2. The host providers.ts still re-exports the FULL public API surface (23
* symbols) so every existing consumer keeps importing from the same path.
* 3. The leaf modules export their own pieces directly.
*
* Pure typeof/behaviour checks only — no function is invoked against the DB,
* so no SQLite handle is opened (no resetDbInstance teardown required).
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
// ── 1. columns.ts — pure helpers behaviour ───────────────────────────────────
import {
normalizeBooleanColumn,
sanitizeRateLimitOverrides,
sanitizeQuotaWindowThresholds,
serializeJsonField,
toRecord,
toStringOrNull,
toNumberOrZero,
} from "../../src/lib/db/providers/columns.ts";
describe("providers/columns — normalizeBooleanColumn", () => {
it("passes through real booleans", () => {
assert.equal(normalizeBooleanColumn(true, false), true);
assert.equal(normalizeBooleanColumn(false, true), false);
});
it("maps SQLite integer flags (1/0)", () => {
assert.equal(normalizeBooleanColumn(1, false), true);
assert.equal(normalizeBooleanColumn(0, true), false);
});
it("parses string flags case-insensitively", () => {
assert.equal(normalizeBooleanColumn("True", false), true);
assert.equal(normalizeBooleanColumn("0", true), false);
});
it("returns the fallback for unrecognized values", () => {
assert.equal(normalizeBooleanColumn("maybe", true), true);
assert.equal(normalizeBooleanColumn(undefined, false), false);
});
});
describe("providers/columns — sanitizeRateLimitOverrides", () => {
it("returns null for nullish / non-object / array input", () => {
assert.equal(sanitizeRateLimitOverrides(null), null);
assert.equal(sanitizeRateLimitOverrides(undefined), null);
assert.equal(sanitizeRateLimitOverrides("x"), null);
assert.equal(sanitizeRateLimitOverrides([1, 2]), null);
});
it("keeps only allowed keys with non-negative integers", () => {
assert.deepEqual(sanitizeRateLimitOverrides({ rpm: 10, bogus: 5, tpm: -1 }), { rpm: 10 });
});
it("returns null when nothing valid remains", () => {
assert.equal(sanitizeRateLimitOverrides({ rpm: 1.5, nope: 3 }), null);
});
});
describe("providers/columns — sanitizeQuotaWindowThresholds", () => {
it("keeps only 0-100 integers", () => {
assert.deepEqual(sanitizeQuotaWindowThresholds({ a: 50, b: 120, c: 0 }), { a: 50, c: 0 });
});
it("returns null when empty", () => {
assert.equal(sanitizeQuotaWindowThresholds({ a: 200 }), null);
});
});
describe("providers/columns — small coercers", () => {
it("serializeJsonField stringifies objects, null otherwise", () => {
assert.equal(serializeJsonField({ a: 1 }), '{"a":1}');
assert.equal(serializeJsonField(null), null);
assert.equal(serializeJsonField("str"), null);
});
it("toRecord returns objects as-is, {} otherwise", () => {
const o = { x: 1 };
assert.equal(toRecord(o), o);
assert.deepEqual(toRecord(null), {});
assert.deepEqual(toRecord(42), {});
});
it("toStringOrNull / toNumberOrZero coerce by type", () => {
assert.equal(toStringOrNull("hi"), "hi");
assert.equal(toStringOrNull(5), null);
assert.equal(toNumberOrZero(7), 7);
assert.equal(toNumberOrZero("7"), 0);
});
});
// ── 2. providers.ts — full public API surface preserved ──────────────────────
const host = await import("../../src/lib/db/providers.ts");
describe("providers.ts public API surface (23 symbols)", () => {
const expected = [
// Connection CRUD (kept in host)
"getProviderConnections",
"getProviderConnectionById",
"createProviderConnection",
"updateProviderConnection",
"deleteProviderConnection",
"deleteProviderConnections",
"deleteProviderConnectionsByProvider",
"reorderProviderConnections",
"cleanupProviderConnections",
"getDistinctGroups",
"autoMigrateLegacyEncryptedConnections",
// Provider nodes (re-exported from ./providers/nodes)
"getProviderNodes",
"getProviderNodeById",
"resolveProviderNodeForConnection",
"createProviderNode",
"updateProviderNode",
"deleteProviderNode",
// Rate-limit / quota runtime (re-exported from ./providers/rateLimit)
"setConnectionRateLimitUntil",
"isConnectionRateLimited",
"getRateLimitedConnections",
"getEffectiveQuotaUsage",
"clearStaleCrashCooldowns",
"formatResetCountdown",
];
for (const name of expected) {
it(`re-exports ${name} as a function`, () => {
assert.equal(typeof host[name], "function", `${name} must be a function on the host module`);
});
}
it("exposes exactly the 23 expected callables (no public symbol lost)", () => {
const missing = expected.filter((n) => typeof host[n] !== "function");
assert.deepEqual(missing, [], `missing public exports: ${missing.join(", ")}`);
});
});
// ── 3. leaf modules export their own pieces ──────────────────────────────────
describe("leaf modules export their slices directly", () => {
it("nodes.ts exports the 6 node CRUD functions", async () => {
const nodes = await import("../../src/lib/db/providers/nodes.ts");
for (const fn of [
"getProviderNodes",
"getProviderNodeById",
"resolveProviderNodeForConnection",
"createProviderNode",
"updateProviderNode",
"deleteProviderNode",
]) {
assert.equal(typeof nodes[fn], "function", `nodes.${fn}`);
}
});
it("rateLimit.ts exports the 6 runtime helpers", async () => {
const rl = await import("../../src/lib/db/providers/rateLimit.ts");
for (const fn of [
"setConnectionRateLimitUntil",
"isConnectionRateLimited",
"getRateLimitedConnections",
"getEffectiveQuotaUsage",
"clearStaleCrashCooldowns",
"formatResetCountdown",
]) {
assert.equal(typeof rl[fn], "function", `rateLimit.${fn}`);
}
});
});