Spaces:
Runtime error
Runtime error
| /** | |
| * 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 }); | |
| fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); | |
| } | |
| test.beforeEach(() => { | |
| resetDb(); | |
| }); | |
| test.after(() => { | |
| core.resetDbInstance(); | |
| fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); | |
| }); | |
| 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"); | |
| }); | |