mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 12:52:25 +03:00
Two shards on release/v3.8.51 went red in one day with the same signature —
"ENOTEMPTY, Directory not empty: /tmp/omniroute-<test>-XXXXXX" — from
combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only
.github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass
alone and on re-run: the cleanup races something still writing into the directory
(SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner
the window opens. 1154 test files do their own cleanup with
fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries.
One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record):
every rm / rmSync / rmdirSync option object with `recursive: true` and no
`maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries
ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292
files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included.
Only the option object changes: no call site, assertion or import is touched.
Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292
files; a random 20-file sample runs green (quota-redis-store hangs identically on
the untouched tree — it needs a Redis on localhost, an environment matter). The
four unit shards on this PR are the full run.
406 lines
14 KiB
TypeScript
406 lines
14 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-felo-runtime-block-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const combosDb = await import("../../src/lib/db/combos.ts");
|
|
const providersDb = await import("../../src/lib/db/providers.ts");
|
|
const providerNodesDb = await import("../../src/lib/db/providers/nodes.ts");
|
|
const modelAliasesDb = await import("../../src/lib/db/models/aliases.ts");
|
|
const settingsDb = await import("../../src/lib/db/settings.ts");
|
|
const modelAliasResolver = await import("../../src/lib/modelAliasResolver.ts");
|
|
const providerPrefixIndex = await import("../../src/lib/providerNodePrefixes.ts");
|
|
const { hashLeaseOwnerId } = await import("../../src/lib/db/exclusiveConnectionLeases.ts");
|
|
const { getProviderCredentials } = await import("../../src/sse/services/auth.ts");
|
|
const { getModelInfo } = await import("../../src/sse/services/model.ts");
|
|
const { resolveModelOrError } = await import("../../src/sse/handlers/chatHelpers.ts");
|
|
const chatRoute = await import("../../src/app/api/v1/chat/completions/route.ts");
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
const RETIRED_PROVIDER_VARIANTS = [
|
|
"felo-web",
|
|
"felo",
|
|
" FeLo-Web ",
|
|
"\tFELO\n",
|
|
"\u00a0FELO-WEB\uFEFF",
|
|
"\u2003felo\u2029",
|
|
"\u3000FELO-WEB\u3000",
|
|
] as const;
|
|
|
|
async function resetStorage() {
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
core.getDbInstance();
|
|
modelAliasResolver.invalidateAliasCache();
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
globalThis.fetch = originalFetch;
|
|
await resetStorage();
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
globalThis.fetch = originalFetch;
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
});
|
|
|
|
test.after(() => {
|
|
globalThis.fetch = originalFetch;
|
|
core.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
});
|
|
|
|
function isRetiredError(error: unknown): boolean {
|
|
const typed = error as Error & { code?: string; status?: number };
|
|
assert.equal(typed.code, "PROVIDER_RETIRED");
|
|
assert.equal(typed.status, 410);
|
|
assert.match(typed.message, /retired/i);
|
|
return true;
|
|
}
|
|
|
|
test("retired Felo model prefixes cannot shadow custom compatible nodes", async () => {
|
|
const nodeIdsByPrefix = new Map<string, string>();
|
|
for (const [index, providerId] of ["felo-web", "felo", "FeLo-WeB", "FELO"].entries()) {
|
|
const nodeId = `openai-compatible-chat-retired-felo-prefix-${index}`;
|
|
await providerNodesDb.createProviderNode({
|
|
id: nodeId,
|
|
type: "openai-compatible",
|
|
name: `Retired prefix ${providerId}`,
|
|
prefix: providerId,
|
|
apiType: "chat",
|
|
baseUrl: "https://retired.example.invalid/v1",
|
|
});
|
|
nodeIdsByPrefix.set(providerId, nodeId);
|
|
|
|
await assert.rejects(() => getModelInfo(`${providerId}/gpt-4o`), isRetiredError);
|
|
}
|
|
|
|
const prefixIndex = await providerPrefixIndex.getProviderPrefixIndex();
|
|
for (const [providerId, nodeId] of nodeIdsByPrefix) {
|
|
assert.equal(
|
|
prefixIndex.entries.get(providerId)?.status,
|
|
"reserved",
|
|
`${providerId} must remain reserved in pricing and override indexes`
|
|
);
|
|
assert.equal(prefixIndex.eligibleNodeIds.has(nodeId), false);
|
|
assert.equal(prefixIndex.prefixToNode.has(providerId), false);
|
|
}
|
|
});
|
|
|
|
test("stripModelPrefix cannot erase retired Felo identities before dispatch", async () => {
|
|
await settingsDb.updateSettings({ stripModelPrefix: true });
|
|
try {
|
|
for (const providerId of ["felo-web", "felo", "FeLo-WeB", "FELO"]) {
|
|
await assert.rejects(() => getModelInfo(`${providerId}/gpt-4o`), isRetiredError);
|
|
}
|
|
} finally {
|
|
await settingsDb.updateSettings({ stripModelPrefix: false });
|
|
}
|
|
});
|
|
|
|
test("direct chat resolution converts retired Felo failures into sanitized HTTP 410", async () => {
|
|
for (const providerId of ["felo-web", "felo"]) {
|
|
const result = await resolveModelOrError(
|
|
`${providerId}/gpt-4o`,
|
|
{ model: `${providerId}/gpt-4o`, messages: [{ role: "user", content: "hello" }] },
|
|
"/v1/chat/completions"
|
|
);
|
|
assert.ok(result.error instanceof Response);
|
|
assert.equal(result.error.status, 410);
|
|
const body = (await result.error.json()) as {
|
|
error?: { code?: string; message?: string };
|
|
};
|
|
assert.equal(body.error?.code, "PROVIDER_RETIRED");
|
|
assert.equal(body.error?.message, "Provider is retired and unavailable.");
|
|
assert.equal(JSON.stringify(body).includes(providerId), false);
|
|
}
|
|
});
|
|
|
|
test("persisted aliases cannot rewrite retired Felo models before the route tombstone", async () => {
|
|
await providersDb.createProviderConnection({
|
|
provider: "openai",
|
|
authType: "apikey",
|
|
name: "Retired Felo alias bypass control",
|
|
apiKey: "sk-retired-felo-alias-bypass",
|
|
isActive: true,
|
|
testStatus: "active",
|
|
});
|
|
await modelAliasesDb.setModelAlias("felo-web/gpt-4o", "openai/gpt-4o");
|
|
modelAliasResolver.invalidateAliasCache();
|
|
|
|
const fetchCalls: string[] = [];
|
|
globalThis.fetch = async (input: string | URL | Request) => {
|
|
fetchCalls.push(String(input));
|
|
return Response.json({
|
|
id: "chatcmpl-retired-felo-alias-bypass",
|
|
choices: [{ message: { role: "assistant", content: "alias bypassed retirement" } }],
|
|
});
|
|
};
|
|
|
|
const response = await chatRoute.POST(
|
|
new Request("http://localhost/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
model: "felo-web/gpt-4o",
|
|
messages: [{ role: "user", content: "hello" }],
|
|
stream: false,
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 410);
|
|
assert.equal(fetchCalls.length, 0, "a retired alias must be rejected before upstream fetch");
|
|
const body = (await response.json()) as {
|
|
error?: { code?: string; message?: string };
|
|
};
|
|
assert.equal(body.error?.code, "PROVIDER_RETIRED");
|
|
assert.equal(body.error?.message, "Provider is retired and unavailable.");
|
|
|
|
// Bare model names are aliases/combos, not provider prefixes. An operator is
|
|
// still allowed to own an unrelated alias named "felo"; only the slashful
|
|
// retired provider identity must be rejected before alias resolution.
|
|
await modelAliasesDb.setModelAlias("felo", "openai/gpt-4o");
|
|
modelAliasResolver.invalidateAliasCache();
|
|
const bareAliasResponse = await chatRoute.POST(
|
|
new Request("http://localhost/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
model: "felo",
|
|
messages: [{ role: "user", content: "hello" }],
|
|
stream: false,
|
|
}),
|
|
})
|
|
);
|
|
assert.equal(bareAliasResponse.status, 200);
|
|
assert.equal(fetchCalls.length, 1, "a bare alias named felo must remain routable");
|
|
});
|
|
|
|
test("priority combo skips retired Felo target and falls back to a healthy target", async () => {
|
|
await providersDb.createProviderConnection({
|
|
provider: "openai",
|
|
authType: "apikey",
|
|
name: "Healthy Felo combo fallback",
|
|
apiKey: "sk-healthy-felo-combo-fallback",
|
|
isActive: true,
|
|
testStatus: "active",
|
|
});
|
|
await combosDb.createCombo({
|
|
name: "retired-felo-fallback",
|
|
strategy: "priority",
|
|
models: [
|
|
{ provider: "felo-web", model: "gpt-4o" },
|
|
{ provider: "openai", model: "gpt-4o" },
|
|
],
|
|
});
|
|
|
|
const fetchCalls: string[] = [];
|
|
globalThis.fetch = async (input: string | URL | Request) => {
|
|
fetchCalls.push(String(input));
|
|
return Response.json({
|
|
id: "chatcmpl-retired-felo-fallback",
|
|
choices: [{ message: { role: "assistant", content: "healthy fallback" } }],
|
|
});
|
|
};
|
|
|
|
const response = await chatRoute.POST(
|
|
new Request("http://localhost/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-OmniRoute-No-Cache": "true",
|
|
},
|
|
body: JSON.stringify({
|
|
model: "retired-felo-fallback",
|
|
messages: [{ role: "user", content: "hello" }],
|
|
stream: false,
|
|
}),
|
|
})
|
|
);
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(fetchCalls.length, 1);
|
|
const body = (await response.json()) as { choices?: Array<{ message?: { content?: string } }> };
|
|
assert.equal(body.choices?.[0]?.message?.content, "healthy fallback");
|
|
});
|
|
|
|
test("retired Felo ids stay ineligible after imports, even if DB triggers are bypassed", async () => {
|
|
const db = core.getDbInstance();
|
|
|
|
const created = await providersDb.createProviderConnection({
|
|
provider: "felo-web",
|
|
authType: "apikey",
|
|
name: "Retired Felo create response",
|
|
apiKey: "retired-felo-create-key",
|
|
isActive: true,
|
|
});
|
|
assert.equal(created.isActive, false, "create must report the tombstoned persisted state");
|
|
|
|
const updated = await providersDb.updateProviderConnection(created.id, {
|
|
isActive: true,
|
|
testStatus: "active",
|
|
errorCode: null,
|
|
lastError: null,
|
|
lastErrorType: null,
|
|
lastErrorSource: null,
|
|
lastErrorAt: null,
|
|
});
|
|
assert.equal(updated?.isActive, false, "update must report the tombstoned persisted state");
|
|
assert.equal(updated?.errorCode, "PROVIDER_REMOVED");
|
|
|
|
const deduplicated = await providersDb.createProviderConnection({
|
|
provider: "felo-web",
|
|
authType: "apikey",
|
|
name: "Retired Felo create response",
|
|
apiKey: "retired-felo-create-key",
|
|
isActive: true,
|
|
testStatus: "active",
|
|
});
|
|
assert.equal(deduplicated.id, created.id, "the second create must use the dedup path");
|
|
assert.equal(
|
|
deduplicated.isActive,
|
|
false,
|
|
"a deduplicated create must report the tombstoned persisted state"
|
|
);
|
|
assert.equal(deduplicated.errorCode, "PROVIDER_REMOVED");
|
|
|
|
await assert.rejects(
|
|
providersDb.updateProviderConnection(created.id, {
|
|
provider: "openai",
|
|
isActive: true,
|
|
testStatus: "active",
|
|
errorCode: null,
|
|
lastError: null,
|
|
lastErrorType: null,
|
|
lastErrorSource: null,
|
|
lastErrorAt: null,
|
|
}),
|
|
/retired provider connection identity cannot be changed/i
|
|
);
|
|
const identityPreserved = await providersDb.getProviderConnectionById(created.id);
|
|
assert.equal(identityPreserved?.provider, "felo-web");
|
|
assert.equal(identityPreserved?.isActive, false);
|
|
assert.equal(identityPreserved?.errorCode, "PROVIDER_REMOVED");
|
|
|
|
for (const [index, providerId] of RETIRED_PROVIDER_VARIANTS.entries()) {
|
|
const connectionId = `trigger-normalized-${index}`;
|
|
db.prepare(
|
|
"INSERT INTO provider_connections " +
|
|
"(id, provider, auth_type, name, is_active, test_status, created_at, updated_at) " +
|
|
"VALUES (?, ?, 'apikey', ?, 1, 'active', datetime('now'), datetime('now'))"
|
|
).run(connectionId, providerId, `${providerId}-post-migration-import`);
|
|
|
|
const persistedState = db
|
|
.prepare(
|
|
"SELECT is_active, test_status, error_code, last_error_type, last_error_source " +
|
|
"FROM provider_connections WHERE id = ?"
|
|
)
|
|
.get(connectionId) as {
|
|
is_active: number;
|
|
test_status: string;
|
|
error_code: string;
|
|
last_error_type: string;
|
|
last_error_source: string;
|
|
};
|
|
assert.deepEqual(persistedState, {
|
|
is_active: 0,
|
|
test_status: "unavailable",
|
|
error_code: "PROVIDER_REMOVED",
|
|
last_error_type: "provider_removed",
|
|
last_error_source: "migration:retire-felo-web",
|
|
});
|
|
|
|
const credentials = await getProviderCredentials(
|
|
providerId,
|
|
null,
|
|
[connectionId],
|
|
"felo-chat",
|
|
{ allowSuppressedConnections: true }
|
|
);
|
|
assert.equal(
|
|
credentials,
|
|
null,
|
|
`${providerId} must remain blocked after trigger normalization`
|
|
);
|
|
}
|
|
|
|
db.exec(`
|
|
DROP TRIGGER IF EXISTS provider_connections_retire_felo_web_insert;
|
|
DROP TRIGGER IF EXISTS provider_connections_retire_felo_web_update;
|
|
DROP TRIGGER IF EXISTS provider_connections_preserve_felo_web_identity_insert;
|
|
DROP TRIGGER IF EXISTS provider_connections_preserve_felo_web_identity_update;
|
|
DROP TRIGGER IF EXISTS exclusive_connection_leases_retire_felo_web_insert;
|
|
DROP TRIGGER IF EXISTS exclusive_connection_leases_retire_felo_web_update;
|
|
`);
|
|
|
|
for (const [index, providerId] of RETIRED_PROVIDER_VARIANTS.entries()) {
|
|
const connectionId = `truly-active-${index}`;
|
|
const leaseOwnerId = `vlo_${String.fromCharCode(65 + index).repeat(43)}`;
|
|
const apiKeyId = `retired-key-${index}`;
|
|
const generation = index + 1;
|
|
const now = new Date().toISOString();
|
|
const expiresAt = new Date(Date.now() + 60_000).toISOString();
|
|
|
|
db.prepare(
|
|
"INSERT INTO provider_connections " +
|
|
"(id, provider, auth_type, name, is_active, test_status, created_at, updated_at) " +
|
|
"VALUES (?, ?, 'apikey', ?, 1, 'active', ?, ?)"
|
|
).run(connectionId, providerId, `${providerId}-trigger-bypass`, now, now);
|
|
db.prepare(
|
|
"INSERT INTO exclusive_connection_leases " +
|
|
"(lease_owner_hash, api_key_id, provider, connection_id, generation, state, " +
|
|
"acquired_at, renewed_at, expires_at) VALUES (?, ?, ?, ?, ?, 'ACTIVE', ?, ?, ?)"
|
|
).run(
|
|
hashLeaseOwnerId(leaseOwnerId),
|
|
apiKeyId,
|
|
providerId,
|
|
connectionId,
|
|
generation,
|
|
now,
|
|
now,
|
|
expiresAt
|
|
);
|
|
|
|
const activeBeforeSelection = db
|
|
.prepare("SELECT is_active, test_status FROM provider_connections WHERE id = ?")
|
|
.get(connectionId) as { is_active: number; test_status: string };
|
|
assert.deepEqual(
|
|
activeBeforeSelection,
|
|
{ is_active: 1, test_status: "active" },
|
|
"fixture must bypass the migration triggers so the auth tombstone is tested independently"
|
|
);
|
|
|
|
const credentials = await getProviderCredentials(
|
|
providerId,
|
|
null,
|
|
[connectionId],
|
|
"felo-chat",
|
|
{
|
|
allowSuppressedConnections: true,
|
|
lease: {
|
|
apiKeyId,
|
|
context: { leaseOwnerId, generation },
|
|
mode: "request",
|
|
},
|
|
}
|
|
);
|
|
assert.equal(credentials, null, `${providerId} must be blocked even with a truly active row`);
|
|
|
|
const lease = db
|
|
.prepare("SELECT state, end_reason FROM exclusive_connection_leases WHERE connection_id = ?")
|
|
.get(connectionId) as { state: string; end_reason: string | null };
|
|
assert.deepEqual(lease, {
|
|
state: "INVALIDATED",
|
|
end_reason: "CONNECTION_INELIGIBLE",
|
|
});
|
|
}
|
|
});
|