Files
OmniRoute/tests/unit/db/serviceModels.test.ts
Diego Rodrigues de Sa e Souza 93265eede3 test(infra): retry recursive temp-dir removal on main (main twin of #11968) (#12246)
* 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.
2026-09-01 01:48:00 -03:00

183 lines
6.9 KiB
TypeScript

/**
* Tests for src/lib/db/serviceModels.ts
*
* Uses an isolated in-memory DB via DATA_DIR override.
*/
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";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-service-models-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.NODE_ENV = "test";
process.env.DISABLE_SQLITE_AUTO_BACKUP = "true";
const core = await import("../../../src/lib/db/core.ts");
const { getServiceModels, saveServiceModels, markAllUnavailable } =
await import("../../../src/lib/db/serviceModels.ts");
function resetDb() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(() => {
resetDb();
});
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
});
test("getServiceModels — returns [] when no row exists", () => {
const result = getServiceModels("9router");
assert.deepEqual(result, []);
});
test("saveServiceModels + getServiceModels — round-trips a list (with available flag)", () => {
const models = [
{ id: "9r/gemma-3n-e4b", name: "Gemma 3n", object: "model", owned_by: "google" },
{ id: "9r/llama-3.3-70b", name: "Llama 3.3 70B", object: "model", owned_by: "meta" },
];
saveServiceModels("9router", models);
const result = getServiceModels("9router");
assert.equal(result.length, 2);
// Models are enriched with available: true on save.
assert.equal(result[0].id, "9r/gemma-3n-e4b");
assert.equal(result[0].available, true);
assert.equal(result[1].id, "9r/llama-3.3-70b");
assert.equal(result[1].available, true);
});
test("saveServiceModels — incoming model replaces old; old model pruned to available=false", () => {
saveServiceModels("9router", [{ id: "old-model" }]);
saveServiceModels("9router", [{ id: "new-model" }]);
const result = getServiceModels("9router");
// Both old and new are present — old is pruned (available=false), new is active.
const byId = Object.fromEntries(result.map((m) => [m.id, m]));
assert.ok("old-model" in byId, "old-model should persist (soft delete)");
assert.equal(byId["old-model"].available, false);
assert.ok("new-model" in byId, "new-model should be present");
assert.equal(byId["new-model"].available, true);
});
test("saveServiceModels — saving empty list marks previous models as unavailable (no hard delete)", () => {
saveServiceModels("9router", [{ id: "some-model" }]);
saveServiceModels("9router", []);
const result = getServiceModels("9router");
// Pruning keeps the model around as unavailable rather than deleting.
assert.equal(result.length, 1);
assert.equal(result[0].id, "some-model");
assert.equal(result[0].available, false);
});
test("models are scoped by tool — different tools don't interfere", () => {
saveServiceModels("9router", [{ id: "nr-model" }]);
saveServiceModels("cliproxyapi", [{ id: "cli-model" }]);
assert.equal(getServiceModels("9router")[0].id, "nr-model");
assert.equal(getServiceModels("cliproxyapi")[0].id, "cli-model");
});
test("getServiceModels — tolerates corrupt JSON by returning []", () => {
const db = core.getDbInstance();
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)").run(
"serviceModels",
"9router",
"not-valid-json{"
);
const result = getServiceModels("9router");
assert.deepEqual(result, []);
});
test("getServiceModels — returns [] when stored value is not an array", () => {
const db = core.getDbInstance();
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)").run(
"serviceModels",
"9router",
JSON.stringify({ id: "not-an-array" })
);
const result = getServiceModels("9router");
assert.deepEqual(result, []);
});
test("saveServiceModels — pruning: models missing from new payload have available=false", () => {
// Initial save with 3 models.
saveServiceModels("9router", [
{ id: "9router/model-a" },
{ id: "9router/model-b" },
{ id: "9router/model-c" },
]);
// Second save — model-b is missing (pruned), model-c stays, model-d is new.
saveServiceModels("9router", [{ id: "9router/model-c" }, { id: "9router/model-d" }]);
const result = getServiceModels("9router");
const byId = Object.fromEntries(result.map((m) => [m.id, m]));
// model-a was in first payload but not second, should be pruned
assert.ok("9router/model-a" in byId, "pruned model-a should still exist (soft delete)");
assert.equal(byId["9router/model-a"].available, false, "pruned model-a should be unavailable");
// model-b was in first payload but not second, should be pruned
assert.ok("9router/model-b" in byId, "pruned model-b should still exist (soft delete)");
assert.equal(byId["9router/model-b"].available, false, "pruned model-b should be unavailable");
// model-c was in both payloads — should be available
assert.equal(byId["9router/model-c"].available, true, "model-c should be available");
// model-d is new — should be available
assert.equal(byId["9router/model-d"].available, true, "model-d should be available");
});
test("saveServiceModels — incoming models are marked available=true", () => {
saveServiceModels("9router", [{ id: "9router/cx/gpt-5-mini", name: "GPT-5 mini" }]);
const result = getServiceModels("9router");
assert.equal(result.length, 1);
assert.equal(result[0].available, true);
});
test("markAllUnavailable — flips all rows for the given tool to available=false", () => {
saveServiceModels("9router", [{ id: "9router/model-x" }, { id: "9router/model-y" }]);
markAllUnavailable("9router");
const result = getServiceModels("9router");
assert.equal(result.length, 2);
for (const m of result) {
assert.equal(m.available, false, `${m.id} should be unavailable after markAllUnavailable`);
}
});
test("markAllUnavailable — does not affect other tools", () => {
saveServiceModels("9router", [{ id: "9router/model-x" }]);
saveServiceModels("cliproxy", [{ id: "cliproxy/model-z" }]);
markAllUnavailable("9router");
const cliproxyModels = getServiceModels("cliproxy");
assert.equal(cliproxyModels.length, 1);
// cliproxy models should not have been touched
assert.notEqual(cliproxyModels[0].available, false, "cliproxy model should not be affected");
});
test("markAllUnavailable — is a no-op when no models stored", () => {
assert.doesNotThrow(() => markAllUnavailable("9router"));
const result = getServiceModels("9router");
assert.deepEqual(result, []);
});
test("available field exists on ServiceModel interface (structural check)", () => {
saveServiceModels("9router", [{ id: "9router/test-model", available: true }]);
const result = getServiceModels("9router");
assert.ok("available" in result[0], "available field should be present in stored model");
});