Files
OmniRoute/tests/unit/cache-config-route-8219.test.ts
Paijo 2a865aaaa7 feat(settings): configurable model catalog cache TTL (#8219)
* feat(settings): configurable model catalog cache TTL

Add modelCatalogCacheTtlMs to DatabaseSettings with default 1500ms.
Extend cache-config API route to accept the new field.
Replace hardcoded catalog cache TTL with dynamic settings value.
Add 'Cache' settings tab at /dashboard/settings/cache with sidebar entry,
i18n keys, header description, and legacy route redirect.

* feat(combo): add model connection filter toggle to ModelSelectModal

Adds a 'Show configured only' checkbox below the search bar in
ModelSelectModal that filters each provider group's models through
hasEligibleConnectionForModel. Toggle state persists in localStorage.

- Import hasEligibleConnectionForModel from domain/connectionModelRules
- showConfiguredOnly state + localStorage persistence
- connectionFilteredGroups memo layered on filteredGroups
- Renders both provider section and empty state from connectionFilteredGroups

* test(combo): add connection filter toggle tests for ModelSelectModal

Three test cases: (1) hide excluded models when toggle on,
(2) show empty state when all models excluded,
(3) drop provider group when all its models excluded.
All 3 tests pass.

* fix(settings): correct cache-config route import + add route/tab coverage

The cache-config route imported get/update helpers from a nonexistent
module (@/lib/localDb/databaseSettings) and called an undefined
updateSettings() in PUT, crashing every request. Import the real
databaseSettings module (matching the sibling database/route.ts
convention) and call updateDatabaseSettings(); idempotencyWindowMs is
routed through the flat @/lib/db/settings module instead, since that is
where it is actually read at runtime (idempotencyLayer.ts,
runtimeSettings.ts) — it was never part of the databaseSettings "cache"
section type.

Also fixes a dashboard-typecheck regression in ModelSelectModal.tsx: the
new connection-filter toggle called hasEligibleConnectionForModel() with
activeProviders entries typed too narrowly to include
providerSpecificData, which real connection objects carry at runtime.

Adds:
- tests/unit/cache-config-route-8219.test.ts: GET/PUT resolve without
  crashing, modelCatalogCacheTtlMs and idempotencyWindowMs round-trip.
- tests/unit/ui/cache-settings-tab-bounds-8219.test.tsx: CacheSettingsTab
  min/max TTL bounds (100ms/60000ms) gate the Save button and surface a
  validation message; in-bounds values PUT correctly.

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

* chore(quality): rebaseline sections.ts for #8219 own-growth

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-07-23 08:46:22 -03:00

84 lines
3.1 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";
// Regression guard for #8219: the cache-config route imported
// `updateSettings`/`getDatabaseSettings`/`updateDatabaseSettings` from a
// non-existent module (`@/lib/localDb/databaseSettings`) and called an
// undefined `updateSettings` in PUT. Any request crashed the route with a
// ReferenceError before this fix. This test proves:
// 1. The route module resolves without throwing on import.
// 2. GET/PUT resolve without crashing.
// 3. `modelCatalogCacheTtlMs` round-trips through PUT then GET.
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-cache-config-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.DISABLE_SQLITE_AUTO_BACKUP = "true";
const core = await import("../../src/lib/db/core.ts");
function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
function makeJsonRequest(method: string, body?: unknown): Request {
return new Request("http://localhost/api/settings/cache-config", {
method,
headers: { "Content-Type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
});
}
test.beforeEach(() => {
resetStorage();
});
test.after(() => {
resetStorage();
});
test("cache-config route resolves and modelCatalogCacheTtlMs round-trips", async (t) => {
// Importing the route module must not throw (RED before the fix: the
// module-level import of a nonexistent path crashed at import time).
const cacheConfigRoute = await import("../../src/app/api/settings/cache-config/route.ts");
await t.test("GET resolves without crashing and returns defaults", async () => {
const response = await cacheConfigRoute.GET(makeJsonRequest("GET") as never);
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(typeof body.modelCatalogCacheTtlMs, "number");
});
await t.test("PUT resolves without crashing and persists the new TTL", async () => {
const putResponse = await cacheConfigRoute.PUT(
makeJsonRequest("PUT", { modelCatalogCacheTtlMs: 4242 }) as never
);
const putBody = await putResponse.json();
assert.equal(putResponse.status, 200);
assert.equal(putBody.ok, true);
const getResponse = await cacheConfigRoute.GET(makeJsonRequest("GET") as never);
const getBody = await getResponse.json();
assert.equal(getResponse.status, 200);
assert.equal(getBody.modelCatalogCacheTtlMs, 4242, "modelCatalogCacheTtlMs must round-trip");
});
await t.test("PUT persists idempotencyWindowMs via the flat settings module", async () => {
const putResponse = await cacheConfigRoute.PUT(
makeJsonRequest("PUT", { idempotencyWindowMs: 9000 }) as never
);
assert.equal(putResponse.status, 200);
const getResponse = await cacheConfigRoute.GET(makeJsonRequest("GET") as never);
const getBody = await getResponse.json();
assert.equal(getBody.idempotencyWindowMs, 9000);
});
});