mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 09:42:15 +03:00
fix(sse): scope Vertex 404s to a per-model lockout via passthroughModels
This commit is contained in:
@@ -30,4 +30,5 @@ export const vertexProvider: RegistryEntry = {
|
||||
{ id: "claude-opus-4-7", name: "Claude Opus 4.7 (Vertex)" },
|
||||
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6 (Vertex)" },
|
||||
],
|
||||
passthroughModels: true,
|
||||
};
|
||||
|
||||
121
tests/unit/vertex-passthrough-model-lockout.test.ts
Normal file
121
tests/unit/vertex-passthrough-model-lockout.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
// Regression guard: after adding passthroughModels: true to Vertex's registry entry, a 404 on
|
||||
// one Vertex model (e.g. a stale/synthetic model id) must lock out only that model, not cool
|
||||
// down the whole connection — mirrors the existing ollama-cloud/bedrock protection. Before this
|
||||
// fix, hasPerModelQuota("vertex", ...) was false, so any 404 on Vertex cooled the whole
|
||||
// connection for COOLDOWN_MS.notFound (2 minutes), per errorConfig.ts's generic status_404 rule.
|
||||
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-vertex-404-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
const providersDb = await import("../../src/lib/db/providers.ts");
|
||||
const auth = await import("../../src/sse/services/auth.ts");
|
||||
const accountFallback = await import("../../open-sse/services/accountFallback.ts");
|
||||
|
||||
async function resetStorage() {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
async function seedVertex() {
|
||||
return providersDb.createProviderConnection({
|
||||
provider: "vertex",
|
||||
authType: "apikey",
|
||||
apiKey: "vertex-key",
|
||||
isActive: true,
|
||||
testStatus: "active",
|
||||
});
|
||||
}
|
||||
|
||||
test.after(() => {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('hasPerModelQuota("vertex", ...) is true after the passthroughModels registry flag', () => {
|
||||
assert.equal(accountFallback.hasPerModelQuota("vertex", "claude-sonnet-5"), true);
|
||||
});
|
||||
|
||||
test("404 on one Vertex model locks only that model, connection stays active", async () => {
|
||||
await resetStorage();
|
||||
const conn = await seedVertex();
|
||||
|
||||
const result = await auth.markAccountUnavailable(
|
||||
conn.id,
|
||||
404,
|
||||
"model not found",
|
||||
"vertex",
|
||||
"claude-sonnet-5-high"
|
||||
);
|
||||
assert.equal(result.shouldFallback, true);
|
||||
|
||||
const after = await providersDb.getProviderConnectionById(conn.id);
|
||||
assert.equal(after.testStatus, "active");
|
||||
assert.ok(!after.rateLimitedUntil, "connection must not be rate-limited");
|
||||
|
||||
const lockout = accountFallback.getModelLockoutInfo("vertex", conn.id, "claude-sonnet-5-high");
|
||||
assert.equal(lockout?.reason, "not_found");
|
||||
|
||||
// A sibling model on the same connection must remain immediately eligible.
|
||||
const sibling = accountFallback.getModelLockoutInfo("vertex", conn.id, "gemini-3.1-pro-preview");
|
||||
assert.equal(sibling, null);
|
||||
});
|
||||
|
||||
test("503 on one Vertex model locks only that model, connection stays active (proves the fix isn't 404-specific)", async () => {
|
||||
// hasPerModelQuota's gate covers status === 404 || status === 429 || status >= 500
|
||||
// (auth.ts:2024) in one shared branch — 502/503/504 keep the model-lockout path (only
|
||||
// the exact 500 is exempted per #5976). This mirrors the 404 test above with a 5xx to
|
||||
// confirm passthroughModels doesn't just fix the specific 404 symptom reported.
|
||||
await resetStorage();
|
||||
const conn = await seedVertex();
|
||||
|
||||
const result = await auth.markAccountUnavailable(
|
||||
conn.id,
|
||||
503,
|
||||
"Service Unavailable",
|
||||
"vertex",
|
||||
"claude-sonnet-5-high"
|
||||
);
|
||||
assert.equal(result.shouldFallback, true);
|
||||
|
||||
const after = await providersDb.getProviderConnectionById(conn.id);
|
||||
assert.equal(after.testStatus, "active");
|
||||
assert.ok(!after.rateLimitedUntil, "connection must not be rate-limited");
|
||||
|
||||
const lockout = accountFallback.getModelLockoutInfo("vertex", conn.id, "claude-sonnet-5-high");
|
||||
assert.equal(lockout?.reason, "server_error");
|
||||
|
||||
const sibling = accountFallback.getModelLockoutInfo("vertex", conn.id, "gemini-3.1-pro-preview");
|
||||
assert.equal(sibling, null);
|
||||
});
|
||||
|
||||
test("403 PERMISSION_DENIED on Vertex locks only that model too (accepted trade-off, see plan)", async () => {
|
||||
await resetStorage();
|
||||
const conn = await seedVertex();
|
||||
|
||||
// Google Cloud uses the literal "PERMISSION_DENIED" status name for BOTH a
|
||||
// model-specific denial and a connection-wide IAM/API-disabled failure — this
|
||||
// fix cannot distinguish them (no live Vertex credential test in this plan), so
|
||||
// it intentionally treats both as a per-model lockout post-passthroughModels.
|
||||
const result = await auth.markAccountUnavailable(
|
||||
conn.id,
|
||||
403,
|
||||
"PERMISSION_DENIED: the caller does not have permission",
|
||||
"vertex",
|
||||
"claude-sonnet-5-high"
|
||||
);
|
||||
assert.equal(result.shouldFallback, true);
|
||||
|
||||
const after = await providersDb.getProviderConnectionById(conn.id);
|
||||
assert.equal(after.testStatus, "active");
|
||||
assert.ok(!after.rateLimitedUntil, "connection must not be rate-limited");
|
||||
|
||||
const lockout = accountFallback.getModelLockoutInfo("vertex", conn.id, "claude-sonnet-5-high");
|
||||
assert.equal(lockout?.reason, "forbidden");
|
||||
});
|
||||
Reference in New Issue
Block a user