Files
OmniRoute/tests/unit/db-schema-columns-split.test.ts
Jan Leon 78d2eee914 Add per-connection Provider Quota visibility (#7360)
* feat(dashboard): add Provider Quota visibility toggle per connection

* refactor(dashboard): extract provider quota visibility controls

Move quota visibility UI and update logic into reusable components, add Portuguese translations, and remove the stale migration gap allowlist entry.

* Hide quota visibility controls for unsupported providers

* chore(ci): retrigger GitHub checks

* fix(db): renumber quota-visibility migration past release tip (121→125)

122_free_proxy_sync_errors.sql, 123_quota_auto_ping.sql, and
124_generic_session_affinity_ttl.sql have since landed on
release/v3.8.49, so 121 is now out-of-sequence and would not apply on
databases already past 122+. Renumbers to 125 (the next free slot past
the current release tip) and restores "121" in check-migration-numbering's
KNOWN_GAPS allowlist, since 121 remains a genuine unfilled gap once this
migration moves off that number.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* chore(quality): rebaseline file-size + complexity for resync merge

The release-resync merge unions two already-compliant features in the
same god-component (ConnectionRow.tsx/ConnectionsListPanel.tsx): this
PR's per-connection quota-visibility wiring and release's confirm-
delete-account wiring (#7361). Both were individually within budget
(785/786 lines); combined they land at 791. Complexity count moves
2058->2059 for the same reason (2 previously-compliant .map() render
callbacks in ConnectionsListPanel.tsx now marginally exceed the
80-line function cap). No new logic was written — see the
_rebaseline_2026_07_18_pr7360_quota_visibility_resync justification
entries in both baseline files for the full accounting. Verified via
a byte-for-byte diff of the violation lists between origin/release/
v3.8.49 tip and this merge. Structural shrink stays tracked in #3501.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(db): split _updateConnectionRow update assembly (complexity gate)

_updateConnectionRow grew past the 80-line max-lines-per-function ceiling
after this branch added quota_visible column handling. Extract the
`.run()` params assembly (field mapping/normalization, unchanged) into a
module-private `_buildUpdateConnectionRowParams` helper in the same file
so the SQL statement + call site stay in `_updateConnectionRow` while the
function itself drops back under the gate. No behavior change.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-18 15:12:52 -03:00

88 lines
3.2 KiB
TypeScript

// Characterization of the db/core.ts schema-column split (god-file decomposition): the idempotent
// ALTER-TABLE column reconcilers + table introspection helpers moved into db/schemaColumns.ts. These
// run an in-memory SQLite db through the helpers to lock the observable behavior: ensure* adds missing
// columns and is safe to re-run; hasTable/hasColumn/getTableColumns/quoteIdentifier introspect.
import { test } from "node:test";
import assert from "node:assert/strict";
import { tryOpenSync } from "../../src/lib/db/adapters/driverFactory.ts";
import {
ensureUsageHistoryColumns,
ensureProviderConnectionsColumns,
hasColumn,
hasTable,
quoteIdentifier,
getTableColumns,
} from "../../src/lib/db/schemaColumns.ts";
function openMemoryDb() {
// Synchronous in-memory adapter — no DATA_DIR / file handles to clean up.
const db = tryOpenSync(":memory:");
assert.ok(db, "expected a synchronous sqlite adapter for :memory:");
return db!;
}
test("quoteIdentifier escapes embedded double quotes", () => {
assert.equal(quoteIdentifier("plain"), '"plain"');
assert.equal(quoteIdentifier('we"ird'), '"we""ird"');
});
test("hasTable / hasColumn / getTableColumns introspect a live table", () => {
const db = openMemoryDb();
try {
db.exec("CREATE TABLE usage_history (id INTEGER PRIMARY KEY, model TEXT)");
assert.equal(hasTable(db, "usage_history"), true);
assert.equal(hasTable(db, "does_not_exist"), false);
assert.equal(hasColumn(db, "usage_history", "model"), true);
assert.equal(hasColumn(db, "usage_history", "nope"), false);
assert.deepEqual(getTableColumns(db, "usage_history").sort(), ["id", "model"]);
} finally {
db.close?.();
}
});
test("ensureUsageHistoryColumns adds missing columns and is idempotent", () => {
const db = openMemoryDb();
try {
db.exec("CREATE TABLE usage_history (id INTEGER PRIMARY KEY, model TEXT)");
assert.equal(hasColumn(db, "usage_history", "service_tier"), false);
ensureUsageHistoryColumns(db);
for (const col of [
"success",
"latency_ms",
"ttft_ms",
"error_code",
"service_tier",
"combo_strategy",
]) {
assert.equal(hasColumn(db, "usage_history", col), true, `expected ${col} after ensure`);
}
// Re-running must not throw (columns already present) — idempotency.
assert.doesNotThrow(() => ensureUsageHistoryColumns(db));
} finally {
db.close?.();
}
});
test("ensureProviderConnectionsColumns repairs quota visibility with a visible default", () => {
const db = openMemoryDb();
try {
db.exec("CREATE TABLE provider_connections (id TEXT PRIMARY KEY, provider TEXT NOT NULL)");
assert.equal(hasColumn(db, "provider_connections", "quota_visible"), false);
ensureProviderConnectionsColumns(db);
assert.equal(hasColumn(db, "provider_connections", "quota_visible"), true);
const column = db
.prepare("PRAGMA table_info(provider_connections)")
.all()
.find((entry: { name?: string }) => entry.name === "quota_visible") as
{ notnull?: number; dflt_value?: string } | undefined;
assert.equal(column?.notnull, 1);
assert.equal(column?.dflt_value, "1");
assert.doesNotThrow(() => ensureProviderConnectionsColumns(db));
} finally {
db.close?.();
}
});