330 lines
10 KiB
TypeScript
330 lines
10 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";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-db-models-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const modelsDb = await import("../../src/lib/db/models.ts");
|
|
|
|
async function resetStorage() {
|
|
core.resetDbInstance();
|
|
|
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
try {
|
|
if (fs.existsSync(TEST_DATA_DIR)) {
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
}
|
|
break;
|
|
} catch (error: any) {
|
|
if ((error?.code === "EBUSY" || error?.code === "EPERM") && attempt < 9) {
|
|
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(async () => {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
test("model aliases can be created, listed and deleted", async () => {
|
|
await modelsDb.setModelAlias("fast-default", { provider: "openai", model: "gpt-4.1-mini" });
|
|
await modelsDb.setModelAlias("reasoning", { provider: "anthropic", model: "claude-3-7-sonnet" });
|
|
|
|
const aliases = await modelsDb.getModelAliases();
|
|
|
|
assert.deepEqual(aliases["fast-default"], {
|
|
provider: "openai",
|
|
model: "gpt-4.1-mini",
|
|
});
|
|
assert.deepEqual(aliases.reasoning, {
|
|
provider: "anthropic",
|
|
model: "claude-3-7-sonnet",
|
|
});
|
|
|
|
await modelsDb.deleteModelAlias("fast-default");
|
|
|
|
assert.equal((await modelsDb.getModelAliases())["fast-default"], undefined);
|
|
});
|
|
|
|
test("MITM aliases support per-tool lookup and aggregated reads", async () => {
|
|
await modelsDb.setMitmAliasAll("cursor", {
|
|
"gpt-4.1": "cursor/gpt-4.1",
|
|
});
|
|
await modelsDb.setMitmAliasAll("codex", {
|
|
"gpt-4.1-mini": "codex/gpt-4.1-mini",
|
|
});
|
|
|
|
assert.deepEqual(await modelsDb.getMitmAlias("cursor"), {
|
|
"gpt-4.1": "cursor/gpt-4.1",
|
|
});
|
|
assert.deepEqual(await modelsDb.getMitmAlias(), {
|
|
cursor: { "gpt-4.1": "cursor/gpt-4.1" },
|
|
codex: { "gpt-4.1-mini": "codex/gpt-4.1-mini" },
|
|
});
|
|
});
|
|
|
|
test("custom models can be added once and queried by provider", async () => {
|
|
const created = await modelsDb.addCustomModel(
|
|
"openrouter",
|
|
"anthropic/claude-3.7-sonnet",
|
|
"Claude 3.7 Sonnet",
|
|
"manual",
|
|
"responses",
|
|
["chat", "responses"]
|
|
);
|
|
const duplicate = await modelsDb.addCustomModel(
|
|
"openrouter",
|
|
"anthropic/claude-3.7-sonnet",
|
|
"Claude 3.7 Sonnet"
|
|
);
|
|
const providerModels = await modelsDb.getCustomModels("openrouter");
|
|
const allModels = await modelsDb.getAllCustomModels();
|
|
|
|
assert.equal(duplicate.id, created.id);
|
|
assert.equal(providerModels.length, 1);
|
|
assert.deepEqual(providerModels[0], created);
|
|
assert.equal((allModels.openrouter as any).length, 1);
|
|
});
|
|
|
|
test("replaceCustomModels preserves compat fields and respects the empty-list guard", async () => {
|
|
await modelsDb.addCustomModel("openai", "gpt-4.1", "GPT-4.1");
|
|
await modelsDb.updateCustomModel("openai", "gpt-4.1", {
|
|
normalizeToolCallId: true,
|
|
preserveOpenAIDeveloperRole: false,
|
|
upstreamHeaders: {
|
|
"X-Test": " enabled ",
|
|
Host: "should-be-removed",
|
|
},
|
|
});
|
|
|
|
const replaced = await modelsDb.replaceCustomModels("openai", [
|
|
{
|
|
id: "gpt-4.1",
|
|
name: "GPT-4.1 Refreshed",
|
|
source: "imported",
|
|
supportsThinking: true,
|
|
},
|
|
]);
|
|
const guarded = await modelsDb.replaceCustomModels("openai", []);
|
|
|
|
assert.equal(replaced[0].normalizeToolCallId, true);
|
|
assert.equal(replaced[0].preserveOpenAIDeveloperRole, false);
|
|
assert.deepEqual(replaced[0].upstreamHeaders, { "X-Test": "enabled" });
|
|
assert.equal(replaced[0].supportsThinking, true);
|
|
assert.equal(guarded.length, 1);
|
|
|
|
await modelsDb.replaceCustomModels("openai", [], { allowEmpty: true });
|
|
|
|
assert.deepEqual(await modelsDb.getCustomModels("openai"), []);
|
|
});
|
|
|
|
test("removing a custom model also removes its compat override", async () => {
|
|
await modelsDb.addCustomModel("anthropic", "claude-3-haiku", "Claude 3 Haiku");
|
|
modelsDb.mergeModelCompatOverride("anthropic", "claude-3-haiku", {
|
|
normalizeToolCallId: true,
|
|
isHidden: true,
|
|
});
|
|
|
|
assert.equal(await modelsDb.removeCustomModel("anthropic", "claude-3-haiku"), true);
|
|
assert.equal(await modelsDb.removeCustomModel("anthropic", "claude-3-haiku"), false);
|
|
assert.deepEqual(await modelsDb.getCustomModels("anthropic"), []);
|
|
assert.deepEqual(modelsDb.getModelCompatOverrides("anthropic"), []);
|
|
});
|
|
|
|
test("synced available models are unioned across connections and cleaned per connection", async () => {
|
|
await modelsDb.replaceSyncedAvailableModelsForConnection("openai", "conn-a", [
|
|
{ id: "gpt-4.1", name: "GPT-4.1", source: "imported" },
|
|
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini", source: "imported" },
|
|
]);
|
|
const union = await modelsDb.replaceSyncedAvailableModelsForConnection("openai", "conn-b", [
|
|
{ id: "gpt-4.1-mini", name: "GPT-4.1 Mini", source: "imported" },
|
|
{ id: "o3-mini", name: "o3-mini", source: "imported" },
|
|
]);
|
|
const remaining = await modelsDb.deleteSyncedAvailableModelsForConnection("openai", "conn-a");
|
|
const allProviders = await modelsDb.getAllSyncedAvailableModels();
|
|
|
|
assert.deepEqual(union.map((model) => model.id).sort(), ["gpt-4.1", "gpt-4.1-mini", "o3-mini"]);
|
|
assert.deepEqual(remaining.map((model) => model.id).sort(), ["gpt-4.1-mini", "o3-mini"]);
|
|
assert.deepEqual(allProviders.openai.map((model) => model.id).sort(), [
|
|
"gpt-4.1-mini",
|
|
"o3-mini",
|
|
]);
|
|
});
|
|
|
|
test("compat overrides expose per-protocol getters and removable extra headers", async () => {
|
|
modelsDb.mergeModelCompatOverride("openai", "gpt-4.1", {
|
|
normalizeToolCallId: true,
|
|
preserveOpenAIDeveloperRole: false,
|
|
isHidden: true,
|
|
upstreamHeaders: {
|
|
"X-Top": "1",
|
|
"bad header": "skip",
|
|
},
|
|
compatByProtocol: {
|
|
openai: {
|
|
normalizeToolCallId: false,
|
|
preserveOpenAIDeveloperRole: true,
|
|
upstreamHeaders: {
|
|
"X-Proto": "yes",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.equal(modelsDb.getModelNormalizeToolCallId("openai", "gpt-4.1"), true);
|
|
assert.equal(modelsDb.getModelNormalizeToolCallId("openai", "gpt-4.1", "openai"), false);
|
|
assert.equal(modelsDb.getModelPreserveOpenAIDeveloperRole("openai", "gpt-4.1"), false);
|
|
assert.equal(modelsDb.getModelPreserveOpenAIDeveloperRole("openai", "gpt-4.1", "openai"), true);
|
|
assert.equal(modelsDb.getModelIsHidden("openai", "gpt-4.1"), true);
|
|
assert.deepEqual(modelsDb.getModelUpstreamExtraHeaders("openai", "gpt-4.1", "openai"), {
|
|
"X-Top": "1",
|
|
"X-Proto": "yes",
|
|
});
|
|
|
|
modelsDb.removeModelCompatOverride("openai", "gpt-4.1");
|
|
|
|
assert.equal(modelsDb.getModelNormalizeToolCallId("openai", "gpt-4.1"), false);
|
|
assert.deepEqual(modelsDb.getModelCompatOverrides("openai"), []);
|
|
});
|
|
|
|
test("sanitizeUpstreamHeadersMap keeps only safe trimmed headers", () => {
|
|
const sanitized = modelsDb.sanitizeUpstreamHeadersMap({
|
|
"X-First": " one ",
|
|
Host: "blocked",
|
|
"Bad Header": "blocked",
|
|
"X-Newline": "bad\nvalue",
|
|
"X-Second": 42,
|
|
});
|
|
|
|
assert.deepEqual(sanitized, {
|
|
"X-First": "one",
|
|
"X-Second": "42",
|
|
});
|
|
});
|
|
|
|
test("compat overrides ignore invalid protocol keys and can be fully removed again", () => {
|
|
modelsDb.mergeModelCompatOverride("openai", "gpt-4.1-mini", {
|
|
normalizeToolCallId: true,
|
|
preserveOpenAIDeveloperRole: true,
|
|
isHidden: true,
|
|
upstreamHeaders: {
|
|
"X-Test": "enabled",
|
|
Host: "blocked",
|
|
},
|
|
compatByProtocol: {
|
|
openai: {
|
|
normalizeToolCallId: false,
|
|
},
|
|
invalid: {
|
|
normalizeToolCallId: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
let overrides = modelsDb.getModelCompatOverrides("openai");
|
|
|
|
assert.equal(overrides.length, 1);
|
|
assert.equal(overrides[0].compatByProtocol.invalid, undefined);
|
|
assert.deepEqual(overrides[0].upstreamHeaders, { "X-Test": "enabled" });
|
|
|
|
modelsDb.mergeModelCompatOverride("openai", "gpt-4.1-mini", {
|
|
normalizeToolCallId: false,
|
|
preserveOpenAIDeveloperRole: null,
|
|
isHidden: null,
|
|
upstreamHeaders: null,
|
|
compatByProtocol: {
|
|
openai: {
|
|
upstreamHeaders: {},
|
|
},
|
|
},
|
|
});
|
|
|
|
overrides = modelsDb.getModelCompatOverrides("openai");
|
|
|
|
assert.deepEqual(overrides, [
|
|
{
|
|
id: "gpt-4.1-mini",
|
|
compatByProtocol: {
|
|
openai: {
|
|
normalizeToolCallId: false,
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test("compat getters fall back to override rows when custom model storage is malformed", async () => {
|
|
await modelsDb.addCustomModel("anthropic", "claude-edge", "Claude Edge");
|
|
await modelsDb.updateCustomModel("anthropic", "claude-edge", {
|
|
normalizeToolCallId: true,
|
|
preserveOpenAIDeveloperRole: false,
|
|
upstreamHeaders: {
|
|
"X-Custom": "top",
|
|
},
|
|
compatByProtocol: {
|
|
openai: {
|
|
normalizeToolCallId: false,
|
|
preserveOpenAIDeveloperRole: true,
|
|
upstreamHeaders: {
|
|
"X-Proto": "proto",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
modelsDb.mergeModelCompatOverride("anthropic", "claude-edge", {
|
|
normalizeToolCallId: true,
|
|
preserveOpenAIDeveloperRole: false,
|
|
isHidden: true,
|
|
upstreamHeaders: {
|
|
"X-Compat": "fallback",
|
|
},
|
|
compatByProtocol: {
|
|
openai: {
|
|
normalizeToolCallId: false,
|
|
preserveOpenAIDeveloperRole: true,
|
|
upstreamHeaders: {
|
|
"X-Compat-Proto": "fallback-proto",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const db = core.getDbInstance();
|
|
db.prepare("UPDATE key_value SET value = ? WHERE namespace = 'customModels' AND key = ?").run(
|
|
"{not-json",
|
|
"anthropic"
|
|
);
|
|
|
|
assert.equal(modelsDb.getModelNormalizeToolCallId("anthropic", "claude-edge", "openai"), false);
|
|
assert.equal(
|
|
modelsDb.getModelPreserveOpenAIDeveloperRole("anthropic", "claude-edge", "openai"),
|
|
true
|
|
);
|
|
assert.equal(modelsDb.getModelIsHidden("anthropic", "claude-edge"), true);
|
|
assert.deepEqual(modelsDb.getModelUpstreamExtraHeaders("anthropic", "claude-edge", "openai"), {
|
|
"X-Compat": "fallback",
|
|
"X-Compat-Proto": "fallback-proto",
|
|
});
|
|
});
|
|
|
|
test("missing alias helpers return empty results for unknown tools and providers", async () => {
|
|
assert.deepEqual(await modelsDb.getMitmAlias("missing-tool"), {});
|
|
assert.deepEqual(await modelsDb.getCustomModels("missing-provider"), []);
|
|
assert.deepEqual(await modelsDb.getAllSyncedAvailableModels(), {});
|
|
});
|