mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
Move chat pipeline validation, circuit breaker execution, proxy resolution, logging, and session header handling into dedicated helpers to keep the SSE handler smaller and easier to verify. Also fix shared API option precedence, rebuild skill version caches after deletions, ignore api.trycloudflare.com false positives, and add rate-limit manager test flush/reset hooks for deterministic coverage. Expand integration and unit coverage across chat routing, auth, cloud sync, skills, executors, streaming, DB helpers, proxy handling, and provider/model utilities.
264 lines
7.9 KiB
JavaScript
264 lines
7.9 KiB
JavaScript
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";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-model-sync-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const coreDb = await import("../../src/lib/db/core.ts");
|
|
const providersDb = await import("../../src/lib/db/providers.ts");
|
|
|
|
async function resetStorage() {
|
|
coreDb.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) {
|
|
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 });
|
|
}
|
|
|
|
function installTimerStubs() {
|
|
const originalSetTimeout = globalThis.setTimeout;
|
|
const originalSetInterval = globalThis.setInterval;
|
|
const originalClearInterval = globalThis.clearInterval;
|
|
const originalClearTimeout = globalThis.clearTimeout;
|
|
const timeouts = [];
|
|
const intervals = [];
|
|
|
|
globalThis.setTimeout = (fn, ms) => {
|
|
const handle = {
|
|
fn,
|
|
ms,
|
|
cleared: false,
|
|
unrefCalled: false,
|
|
unref() {
|
|
this.unrefCalled = true;
|
|
return this;
|
|
},
|
|
};
|
|
timeouts.push(handle);
|
|
return handle;
|
|
};
|
|
|
|
globalThis.setInterval = (fn, ms) => {
|
|
const handle = {
|
|
fn,
|
|
ms,
|
|
cleared: false,
|
|
unrefCalled: false,
|
|
unref() {
|
|
this.unrefCalled = true;
|
|
return this;
|
|
},
|
|
};
|
|
intervals.push(handle);
|
|
return handle;
|
|
};
|
|
|
|
globalThis.clearTimeout = (handle) => {
|
|
if (handle) {
|
|
handle.cleared = true;
|
|
}
|
|
};
|
|
|
|
globalThis.clearInterval = (handle) => {
|
|
if (handle) {
|
|
handle.cleared = true;
|
|
}
|
|
};
|
|
|
|
return {
|
|
timeouts,
|
|
intervals,
|
|
restore() {
|
|
globalThis.setTimeout = originalSetTimeout;
|
|
globalThis.setInterval = originalSetInterval;
|
|
globalThis.clearInterval = originalClearInterval;
|
|
globalThis.clearTimeout = originalClearTimeout;
|
|
},
|
|
};
|
|
}
|
|
|
|
async function loadScheduler(label) {
|
|
const modulePath = path.join(process.cwd(), "src/shared/services/modelSyncScheduler.ts");
|
|
return import(`${pathToFileURL(modulePath).href}?case=${label}-${Date.now()}`);
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
delete process.env.MODEL_SYNC_INTERVAL_HOURS;
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(async () => {
|
|
coreDb.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
test("modelSyncScheduler: internal auth headers validate only for scheduler requests", async () => {
|
|
const {
|
|
buildModelSyncInternalHeaders,
|
|
getModelSyncInternalAuthHeaderName,
|
|
isModelSyncInternalRequest,
|
|
} = await import("../../src/shared/services/modelSyncScheduler.ts");
|
|
|
|
const internalRequest = new Request("http://localhost/api/providers/test/sync-models", {
|
|
method: "POST",
|
|
headers: buildModelSyncInternalHeaders(),
|
|
});
|
|
assert.equal(isModelSyncInternalRequest(internalRequest), true);
|
|
|
|
const externalRequest = new Request("http://localhost/api/providers/test/sync-models", {
|
|
method: "POST",
|
|
headers: { [getModelSyncInternalAuthHeaderName()]: "invalid-token" },
|
|
});
|
|
assert.equal(isModelSyncInternalRequest(externalRequest), false);
|
|
});
|
|
|
|
test("initCloudSync: startup initialization also starts model sync scheduler", () => {
|
|
const filePath = path.join(process.cwd(), "src/lib/initCloudSync.ts");
|
|
const source = fs.readFileSync(filePath, "utf8");
|
|
|
|
assert.match(source, /startModelSyncScheduler\s*\(/);
|
|
});
|
|
|
|
test("proxy: internal model sync token is only allowed for provider model sync routes", () => {
|
|
const filePath = path.join(process.cwd(), "src/proxy.ts");
|
|
const source = fs.readFileSync(filePath, "utf8");
|
|
|
|
assert.match(source, /isModelSyncInternalRequest/);
|
|
assert.match(source, /sync-models\|models/);
|
|
});
|
|
|
|
test("modelSyncScheduler starts once, honors env interval and syncs only active autoSync connections", async () => {
|
|
await providersDb.createProviderConnection({
|
|
provider: "openai",
|
|
authType: "apikey",
|
|
name: "Auto Sync 1",
|
|
apiKey: "sk-auto-1",
|
|
providerSpecificData: { autoSync: true },
|
|
});
|
|
await providersDb.createProviderConnection({
|
|
provider: "openai",
|
|
authType: "apikey",
|
|
name: "Manual Sync",
|
|
apiKey: "sk-manual",
|
|
providerSpecificData: { autoSync: false },
|
|
});
|
|
await providersDb.createProviderConnection({
|
|
provider: "anthropic",
|
|
authType: "apikey",
|
|
name: "Disabled Auto Sync",
|
|
apiKey: "sk-auto-2",
|
|
isActive: false,
|
|
providerSpecificData: { autoSync: true },
|
|
});
|
|
|
|
process.env.MODEL_SYNC_INTERVAL_HOURS = "6";
|
|
const timers = installTimerStubs();
|
|
const fetchCalls = [];
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
globalThis.fetch = async (url, options) => {
|
|
fetchCalls.push({ url, options });
|
|
return new Response(JSON.stringify({ syncedModels: 4 }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
};
|
|
|
|
try {
|
|
const scheduler = await loadScheduler("active-connections");
|
|
|
|
scheduler.startModelSyncScheduler("http://127.0.0.1:7777", 1000);
|
|
scheduler.startModelSyncScheduler("http://127.0.0.1:8888", 9999);
|
|
|
|
assert.equal(timers.timeouts.length, 1);
|
|
assert.equal(timers.timeouts[0].ms, 5000);
|
|
assert.equal(timers.timeouts[0].unrefCalled, true);
|
|
assert.equal(timers.intervals.length, 1);
|
|
assert.equal(timers.intervals[0].ms, 6 * 60 * 60 * 1000);
|
|
assert.equal(timers.intervals[0].unrefCalled, true);
|
|
|
|
await timers.timeouts[0].fn();
|
|
|
|
assert.equal(fetchCalls.length, 1);
|
|
assert.match(fetchCalls[0].url, /\/api\/providers\/.*\/sync-models$/);
|
|
assert.equal(fetchCalls[0].options.method, "POST");
|
|
assert.equal(fetchCalls[0].options.headers["Content-Type"], "application/json");
|
|
assert.equal(
|
|
fetchCalls[0].options.headers[scheduler.getModelSyncInternalAuthHeaderName()],
|
|
scheduler.buildModelSyncInternalHeaders()[scheduler.getModelSyncInternalAuthHeaderName()]
|
|
);
|
|
|
|
const lastRun = await scheduler.getLastModelSyncTime();
|
|
assert.match(lastRun, /^\d{4}-\d{2}-\d{2}T/);
|
|
|
|
scheduler.stopModelSyncScheduler();
|
|
assert.equal(timers.intervals[0].cleared, true);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
timers.restore();
|
|
}
|
|
});
|
|
|
|
test("modelSyncScheduler skips empty cycles and tolerates failing sync requests", async () => {
|
|
const timers = installTimerStubs();
|
|
const originalFetch = globalThis.fetch;
|
|
const fetchCalls = [];
|
|
|
|
globalThis.fetch = async (url) => {
|
|
fetchCalls.push(url);
|
|
return new Response(JSON.stringify({ error: "upstream unavailable" }), {
|
|
status: 503,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
};
|
|
|
|
try {
|
|
const emptyScheduler = await loadScheduler("empty-cycle");
|
|
emptyScheduler.startModelSyncScheduler("http://127.0.0.1:5555", 10_000);
|
|
await timers.timeouts[0].fn();
|
|
assert.equal(fetchCalls.length, 0);
|
|
assert.equal(await emptyScheduler.getLastModelSyncTime(), null);
|
|
emptyScheduler.stopModelSyncScheduler();
|
|
|
|
timers.timeouts.length = 0;
|
|
timers.intervals.length = 0;
|
|
|
|
await providersDb.createProviderConnection({
|
|
provider: "gemini",
|
|
authType: "apikey",
|
|
name: "Auto Sync Failure",
|
|
apiKey: "sk-auto-failure",
|
|
providerSpecificData: { autoSync: true },
|
|
});
|
|
|
|
const failingScheduler = await loadScheduler("failing-cycle");
|
|
failingScheduler.startModelSyncScheduler("http://127.0.0.1:5555", 10_000);
|
|
await timers.timeouts[0].fn();
|
|
|
|
assert.equal(fetchCalls.length, 1);
|
|
assert.match(await failingScheduler.getLastModelSyncTime(), /^\d{4}-\d{2}-\d{2}T/);
|
|
|
|
failingScheduler.stopModelSyncScheduler();
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
timers.restore();
|
|
}
|
|
});
|