mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +03:00
* test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) 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. * fix(quality): let check-forgotten-sibling-tests read a 1,000-file diff The gate shells out to `git diff` through execFileSync with Node's default 1 MB maxBuffer; the 1,292-file codemod in this PR is the first diff large enough to overflow it, and the gate died with `spawnSync git ENOBUFS` before comparing anything. 64 MB is far above any real PR and costs nothing when unused.
676 lines
22 KiB
TypeScript
676 lines
22 KiB
TypeScript
/**
|
|
* Connection-aware expansion for all combo strategies.
|
|
*
|
|
* Group-B strategies (priority / weighted / round-robin / random / p2c /
|
|
* least-used / cost-optimized / lkgp / fill-first / strict-random /
|
|
* context-optimized / cache-optimized / context-relay / fusion / pipeline)
|
|
* historically resolved targets WITHOUT a per-connection view: exhausted
|
|
* accounts kept getting picked. This suite pins the new opt-in pipeline
|
|
* stage `expandTargetsForAllStrategies` -- reusing the A-group expander
|
|
* (`expandTargetsByQuotaAwareConnections`) -- behind the
|
|
* `connectionAwareExpansion` config key (default false).
|
|
*
|
|
* T1-T10 mirror spec section 6 verbatim.
|
|
*/
|
|
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 { randomUUID } from "node:crypto";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-conn-aware-"));
|
|
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");
|
|
const quotaCache = await import("../../../src/domain/quotaCache.ts");
|
|
const { registerQuotaFetcher } = await import("../../../open-sse/services/quotaPreflight.ts");
|
|
const {
|
|
expandTargetsForAllStrategies,
|
|
shouldApplyConnectionAwareExpansion,
|
|
CONNECTION_AWARE_EXPANSION_GROUP_B,
|
|
} = await import("../../../open-sse/services/combo/connectionAwareExpansion.ts");
|
|
const { handleComboChat } = await import("../../../open-sse/services/combo.ts");
|
|
const { lockExactModel } = await import("../../../open-sse/services/accountFallback.ts");
|
|
const { tryPipelineDispatch } = await import("../../../open-sse/services/combo/dispatchPrelude.ts");
|
|
const { resolveComboSetupConfig } = await import("../../../open-sse/services/comboConfig.ts");
|
|
|
|
const noopLog = { warn: () => {}, info: () => {}, debug: () => {}, error: () => {} };
|
|
|
|
function makeTarget(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
kind: "model" as const,
|
|
stepId: "step-1",
|
|
executionKey: "step-1",
|
|
modelStr: "test-provider/model-x",
|
|
provider: "test-provider",
|
|
providerId: null,
|
|
connectionId: null,
|
|
weight: 1,
|
|
label: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test.after(() => {
|
|
coreDb.resetDbInstance();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
});
|
|
|
|
// Gate: strategy + config resolution
|
|
|
|
test("T0a: group B strategies are the 15 non-quota-aware strategies", () => {
|
|
const groupA = new Set(["reset-aware", "reset-window", "headroom", "quota-share", "auto"]);
|
|
for (const strategy of CONNECTION_AWARE_EXPANSION_GROUP_B) {
|
|
assert.ok(!groupA.has(strategy), `group B must not contain A-group strategy ${strategy}`);
|
|
}
|
|
assert.equal(CONNECTION_AWARE_EXPANSION_GROUP_B.length, 15);
|
|
});
|
|
|
|
test("T0b: gate is closed by default and for A-group strategies", () => {
|
|
assert.equal(
|
|
shouldApplyConnectionAwareExpansion("priority", {}),
|
|
false,
|
|
"default off for group B"
|
|
);
|
|
assert.equal(
|
|
shouldApplyConnectionAwareExpansion("priority", { connectionAwareExpansion: true }),
|
|
true,
|
|
"on for group B when enabled"
|
|
);
|
|
assert.equal(
|
|
shouldApplyConnectionAwareExpansion("priority", null, { connectionAwareExpansion: true }),
|
|
true,
|
|
"fallback to settings when combo config is unset"
|
|
);
|
|
assert.equal(
|
|
shouldApplyConnectionAwareExpansion(
|
|
"priority",
|
|
{ connectionAwareExpansion: false },
|
|
{ connectionAwareExpansion: true }
|
|
),
|
|
false,
|
|
"combo config overrides settings"
|
|
);
|
|
assert.equal(
|
|
shouldApplyConnectionAwareExpansion("reset-aware", {
|
|
connectionAwareExpansion: true,
|
|
}),
|
|
false,
|
|
"A-group strategies never double-expand"
|
|
);
|
|
});
|
|
|
|
// T1: expansion filters exhausted, tags healthy @connectionId
|
|
|
|
test("T1: B-group target expands to healthy connections only (handleSingleModel receives healthy connectionId)", async () => {
|
|
const provider = "cae-t1-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const healthy = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "healthy",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
const limited = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "limited",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
rateLimitedUntil: new Date(Date.now() + 60_000).toISOString(),
|
|
});
|
|
|
|
const target = makeTarget({
|
|
modelStr: `${provider}/model-x`,
|
|
provider,
|
|
});
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "priority",
|
|
targets: [target],
|
|
comboName: "cae-t1",
|
|
config: { connectionAwareExpansion: true },
|
|
log: noopLog,
|
|
});
|
|
|
|
assert.equal(out.length, 1, "one expanded target (healthy connection only)");
|
|
assert.equal(out[0].connectionId, healthy.id);
|
|
assert.ok(out[0].executionKey.includes("@" + healthy.id));
|
|
assert.ok(
|
|
!out.some((t) => t.connectionId === limited.id),
|
|
"rate-limited connection must be filtered out"
|
|
);
|
|
|
|
// End-to-end through handleComboChat
|
|
let receivedConnectionId: string | null = null;
|
|
const res = await handleComboChat({
|
|
body: { messages: [{ role: "user", content: "hi" }] },
|
|
combo: {
|
|
name: "cae-t1-combo-" + randomUUID(),
|
|
strategy: "priority",
|
|
models: [`${provider}/model-x`],
|
|
config: { connectionAwareExpansion: true },
|
|
},
|
|
handleSingleModel: async (_b, _m, t) => {
|
|
receivedConnectionId =
|
|
(t as { connectionId?: string | null } | undefined)?.connectionId ?? null;
|
|
return new Response(JSON.stringify({ choices: [] }), { status: 200 });
|
|
},
|
|
isModelAvailable: async () => true,
|
|
log: noopLog,
|
|
settings: null,
|
|
allCombos: null,
|
|
});
|
|
assert.equal(res.status, 200);
|
|
assert.equal(
|
|
receivedConnectionId,
|
|
healthy.id,
|
|
"handleSingleModel must receive the healthy connectionId"
|
|
);
|
|
});
|
|
|
|
// T2: switch off = byte-identical passthrough
|
|
|
|
test("T2: switch off keeps targets byte-identical (no @ suffix)", async () => {
|
|
const provider = "cae-t2-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "only",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
|
|
const target = makeTarget({ modelStr: `${provider}/model-x`, provider });
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "priority",
|
|
targets: [target],
|
|
comboName: "cae-t2",
|
|
config: { connectionAwareExpansion: false },
|
|
log: noopLog,
|
|
});
|
|
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].connectionId, null, "no connection pinning when off");
|
|
assert.equal(out[0].executionKey, "step-1", "no @connectionId suffix when off");
|
|
});
|
|
|
|
test("T2b: global setting enables expansion when combo config is unset", async () => {
|
|
const provider = "cae-t2b-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const healthy = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "healthy",
|
|
isActive: true,
|
|
});
|
|
|
|
let receivedConnectionId: string | null = null;
|
|
const res = await handleComboChat({
|
|
body: { messages: [{ role: "user", content: "hi" }] },
|
|
combo: {
|
|
name: "cae-t2b-combo-" + randomUUID(),
|
|
strategy: "priority",
|
|
models: [`${provider}/model-x`],
|
|
},
|
|
handleSingleModel: async (_b, _m, target) => {
|
|
receivedConnectionId =
|
|
(target as { connectionId?: string | null } | undefined)?.connectionId ?? null;
|
|
return new Response(JSON.stringify({ choices: [] }), { status: 200 });
|
|
},
|
|
isModelAvailable: async () => true,
|
|
log: noopLog,
|
|
settings: { connectionAwareExpansion: true },
|
|
allCombos: null,
|
|
});
|
|
|
|
assert.equal(res.status, 200);
|
|
assert.equal(receivedConnectionId, healthy.id);
|
|
});
|
|
|
|
// T3: provider without a quota fetcher is not expanded
|
|
|
|
test("T3: no-fetcher provider passes through untouched", async () => {
|
|
const provider = "cae-t3-nofetcher-" + randomUUID();
|
|
// NOTE: no registerQuotaFetcher call for this provider.
|
|
await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "only",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
|
|
const target = makeTarget({ modelStr: `${provider}/model-x`, provider });
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "random",
|
|
targets: [target],
|
|
comboName: "cae-t3",
|
|
config: { connectionAwareExpansion: true },
|
|
log: noopLog,
|
|
});
|
|
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].connectionId, null);
|
|
assert.equal(out[0].executionKey, "step-1");
|
|
});
|
|
|
|
// T4: pinned connection that is exhausted drops the target
|
|
|
|
test("T4: pinned exhausted connection drops the whole target", async () => {
|
|
const provider = "cae-t4-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const pinned = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "pinned",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
rateLimitedUntil: new Date(Date.now() + 120_000).toISOString(),
|
|
});
|
|
|
|
const target = makeTarget({
|
|
modelStr: `${provider}/model-x`,
|
|
provider,
|
|
connectionId: pinned.id,
|
|
});
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "priority",
|
|
targets: [target],
|
|
comboName: "cae-t4",
|
|
config: { connectionAwareExpansion: true },
|
|
log: noopLog,
|
|
});
|
|
|
|
assert.equal(out.length, 0, "pinned exhausted target must be dropped entirely");
|
|
});
|
|
|
|
// T5: allowedConnectionIds intersects with the active pool
|
|
|
|
test("T5: step allowlist intersects with active connections", async () => {
|
|
const provider = "cae-t5-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const connA = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "a",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
const connB = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "b",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
|
|
const target = makeTarget({
|
|
modelStr: `${provider}/model-x`,
|
|
provider,
|
|
allowedConnectionIds: [connA.id],
|
|
});
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "round-robin",
|
|
targets: [target],
|
|
comboName: "cae-t5",
|
|
config: { connectionAwareExpansion: true },
|
|
log: noopLog,
|
|
});
|
|
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].connectionId, connA.id);
|
|
assert.ok(!out.some((t) => t.connectionId === connB.id));
|
|
});
|
|
|
|
// T6: RR rotation granularity becomes model x connection
|
|
|
|
test("T6: RR rotation over expanded targets spans connections across consecutive requests", async () => {
|
|
const provider = "cae-t6-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const ids: string[] = [];
|
|
for (let i = 0; i < 3; i++) {
|
|
const conn = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "conn" + i,
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
ids.push(conn.id);
|
|
}
|
|
|
|
const comboName = "cae-t6-combo-" + randomUUID();
|
|
const combo = {
|
|
name: comboName,
|
|
strategy: "round-robin",
|
|
models: [`${provider}/model-x`],
|
|
config: { connectionAwareExpansion: true },
|
|
};
|
|
|
|
const dispatchedConnections: string[] = [];
|
|
for (let i = 0; i < 3; i++) {
|
|
const res = await handleComboChat({
|
|
body: { messages: [{ role: "user", content: `req-${i}` }] },
|
|
combo,
|
|
handleSingleModel: async (_b, _m, t) => {
|
|
dispatchedConnections.push(
|
|
(t as { connectionId?: string | null } | undefined)?.connectionId || ""
|
|
);
|
|
return new Response(JSON.stringify({ choices: [] }), { status: 200 });
|
|
},
|
|
isModelAvailable: async () => true,
|
|
log: noopLog,
|
|
settings: null,
|
|
allCombos: null,
|
|
});
|
|
assert.equal(res.status, 200);
|
|
}
|
|
|
|
assert.equal(dispatchedConnections.length, 3);
|
|
const distinct = new Set(dispatchedConnections);
|
|
assert.equal(distinct.size, 3, "consecutive RR requests must hit 3 distinct connections");
|
|
for (const id of ids) {
|
|
assert.ok(distinct.has(id), `connection ${id} must be hit`);
|
|
}
|
|
});
|
|
|
|
// T7: main loop model lockout gate skips locked connection
|
|
|
|
test("T7: main loop model lockout gate (combo.ts:1286) skips locked target", async () => {
|
|
const provider = "cae-t7-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const connLocked = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "locked",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
const connHealthy = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "healthy",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
|
|
// Lock model-x on connLocked
|
|
lockExactModel(provider, connLocked.id, "model-x", "cooldown", 60_000);
|
|
|
|
const logs: string[] = [];
|
|
const captureLog = {
|
|
warn: () => {},
|
|
info: (_cat: string, msg: string) => {
|
|
logs.push(msg);
|
|
},
|
|
debug: () => {},
|
|
error: () => {},
|
|
};
|
|
|
|
const dispatchedConnections: string[] = [];
|
|
const res = await handleComboChat({
|
|
body: { messages: [{ role: "user", content: "test" }] },
|
|
combo: {
|
|
name: "cae-t7-combo-" + randomUUID(),
|
|
strategy: "priority",
|
|
models: [`${provider}/model-x`],
|
|
config: { connectionAwareExpansion: true },
|
|
},
|
|
handleSingleModel: async (_b, _m, t) => {
|
|
dispatchedConnections.push(
|
|
(t as { connectionId?: string | null } | undefined)?.connectionId || ""
|
|
);
|
|
return new Response(JSON.stringify({ choices: [] }), { status: 200 });
|
|
},
|
|
isModelAvailable: async () => true,
|
|
log: captureLog,
|
|
settings: null,
|
|
allCombos: null,
|
|
});
|
|
|
|
assert.equal(res.status, 200);
|
|
assert.deepEqual(
|
|
dispatchedConnections,
|
|
[connHealthy.id],
|
|
"only the unlocked healthy connection is dispatched"
|
|
);
|
|
assert.ok(
|
|
logs.some((msg) => msg.includes("model locked by resilience")),
|
|
"must log that model is locked"
|
|
);
|
|
});
|
|
|
|
// T8: fusion panel sizing is not inflated by expansion
|
|
|
|
test("T8: fusion panel does not inflate; members carry vetted connectionId", async () => {
|
|
const provider1 = "cae-t8-p1-" + randomUUID();
|
|
const provider2 = "cae-t8-p2-" + randomUUID();
|
|
registerQuotaFetcher(provider1, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
registerQuotaFetcher(provider2, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
|
|
const p1Conns = [];
|
|
for (let i = 0; i < 3; i++) {
|
|
p1Conns.push(
|
|
await providersDb.createProviderConnection({
|
|
provider: provider1,
|
|
authType: "apikey",
|
|
name: "p1-c" + i,
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
})
|
|
);
|
|
}
|
|
const p2Conns = [];
|
|
for (let i = 0; i < 3; i++) {
|
|
p2Conns.push(
|
|
await providersDb.createProviderConnection({
|
|
provider: provider2,
|
|
authType: "apikey",
|
|
name: "p2-c" + i,
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
})
|
|
);
|
|
}
|
|
|
|
const combo = {
|
|
name: "cae-t8-fusion-" + randomUUID(),
|
|
strategy: "fusion",
|
|
models: [`${provider1}/model-a`, `${provider2}/model-b`],
|
|
config: { connectionAwareExpansion: true, judgeModel: "judge-p/judge-m" },
|
|
};
|
|
|
|
const dispatchedPanelTargets: Array<{ modelStr: string; connectionId?: string | null }> = [];
|
|
const res = await handleComboChat({
|
|
body: { messages: [{ role: "user", content: "fuse" }] },
|
|
combo,
|
|
handleSingleModel: async (_b, modelStr, t) => {
|
|
dispatchedPanelTargets.push({
|
|
modelStr,
|
|
connectionId: (t as { connectionId?: string | null } | undefined)?.connectionId,
|
|
});
|
|
return new Response(
|
|
JSON.stringify({
|
|
choices: [{ message: { content: `response-from-${modelStr}` } }],
|
|
}),
|
|
{ status: 200 }
|
|
);
|
|
},
|
|
isModelAvailable: async () => true,
|
|
log: noopLog,
|
|
settings: null,
|
|
allCombos: null,
|
|
});
|
|
|
|
assert.equal(res.status, 200);
|
|
// Panel size should equal 2 (one per model), plus 1 judge synthesis call (or panel members)
|
|
// Each panel member dispatched should have a vetted connectionId
|
|
const p1Calls = dispatchedPanelTargets.filter((c) => c.modelStr === `${provider1}/model-a`);
|
|
const p2Calls = dispatchedPanelTargets.filter((c) => c.modelStr === `${provider2}/model-b`);
|
|
assert.equal(p1Calls.length, 1, "model-a should be called once in panel");
|
|
assert.equal(p2Calls.length, 1, "model-b should be called once in panel");
|
|
assert.equal(
|
|
p1Calls[0].connectionId,
|
|
p1Conns[0].id,
|
|
"model-a should carry first healthy connection"
|
|
);
|
|
assert.equal(
|
|
p2Calls[0].connectionId,
|
|
p2Conns[0].id,
|
|
"model-b should carry first healthy connection"
|
|
);
|
|
});
|
|
|
|
// T9: quotaCache snapshot drives filtering
|
|
|
|
test("T9: isQuotaExhaustedForRequest=true connection is filtered", async () => {
|
|
const provider = "cae-t9-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const exhausted = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "exhausted",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
const healthy = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "healthy",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
// Standard provider: aggregate exhausted=true with a far-future reset.
|
|
quotaCache.setQuotaCache(exhausted.id, provider, {
|
|
default: { remainingPercentage: 0, resetAt: new Date(Date.now() + 86_400_000).toISOString() },
|
|
});
|
|
|
|
const target = makeTarget({ modelStr: `${provider}/model-x`, provider });
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "priority",
|
|
targets: [target],
|
|
comboName: "cae-t9",
|
|
config: { connectionAwareExpansion: true },
|
|
log: noopLog,
|
|
});
|
|
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].connectionId, healthy.id);
|
|
});
|
|
|
|
// T10: fail-open on connection-list load error
|
|
|
|
test("T10: load failure returns the original targets (fail-open)", async () => {
|
|
const provider = "cae-t10-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const target = makeTarget({ modelStr: `${provider}/model-x`, provider });
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "priority",
|
|
targets: [target],
|
|
comboName: "cae-t10",
|
|
config: { connectionAwareExpansion: true },
|
|
log: noopLog,
|
|
__testExpander: async () => {
|
|
throw new Error("synthetic load failure");
|
|
},
|
|
});
|
|
assert.ok(out[0] === target, "fail-open must return the ORIGINAL target objects");
|
|
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].connectionId, null);
|
|
assert.equal(out[0].executionKey, "step-1");
|
|
});
|
|
|
|
// API-key allowedConnections intersects
|
|
|
|
test("apiKey allowedConnections intersects the expanded pool", async () => {
|
|
const provider = "cae-apikey-" + randomUUID();
|
|
registerQuotaFetcher(provider, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
const connA = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "a",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
const connB = await providersDb.createProviderConnection({
|
|
provider,
|
|
authType: "apikey",
|
|
name: "b",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
|
|
const target = makeTarget({ modelStr: `${provider}/model-x`, provider });
|
|
const out = await expandTargetsForAllStrategies({
|
|
strategy: "priority",
|
|
targets: [target],
|
|
comboName: "cae-apikey",
|
|
config: { connectionAwareExpansion: true },
|
|
log: noopLog,
|
|
apiKeyAllowedConnectionIds: [connA.id],
|
|
});
|
|
|
|
assert.equal(out.length, 1);
|
|
assert.equal(out[0].connectionId, connA.id);
|
|
assert.ok(!out.some((t) => t.connectionId === connB.id));
|
|
});
|
|
|
|
// Pipeline dispatch
|
|
|
|
test("Pipeline strategy: each stage picks the first healthy connection", async () => {
|
|
const provider1 = "cae-pipe-p1-" + randomUUID();
|
|
const provider2 = "cae-pipe-p2-" + randomUUID();
|
|
registerQuotaFetcher(provider1, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
registerQuotaFetcher(provider2, async () => ({ used: 0, total: 100, percentUsed: 0 }));
|
|
|
|
const p1Conn = await providersDb.createProviderConnection({
|
|
provider: provider1,
|
|
authType: "apikey",
|
|
name: "p1-c0",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
const p2Conn = await providersDb.createProviderConnection({
|
|
provider: provider2,
|
|
authType: "apikey",
|
|
name: "p2-c0",
|
|
apiKey: "key-" + randomUUID(),
|
|
isActive: true,
|
|
});
|
|
|
|
const combo = {
|
|
name: "cae-pipe-combo-" + randomUUID(),
|
|
strategy: "pipeline",
|
|
models: [`${provider1}/model-1`, `${provider2}/model-2`],
|
|
config: { connectionAwareExpansion: true },
|
|
};
|
|
|
|
const executedStages: string[] = [];
|
|
const res = await tryPipelineDispatch({
|
|
body: { messages: [{ role: "user", content: "pipe" }] },
|
|
combo,
|
|
config: resolveComboSetupConfig(combo, null),
|
|
strategy: "pipeline",
|
|
handleSingleModelWithTimeout: async (_b, modelStr, target) => {
|
|
executedStages.push(
|
|
`${modelStr}@${(target as { connectionId?: string | null } | undefined)?.connectionId}`
|
|
);
|
|
return new Response(JSON.stringify({ choices: [{ message: { content: "stage-out" } }] }), {
|
|
status: 200,
|
|
});
|
|
},
|
|
log: noopLog,
|
|
});
|
|
|
|
assert.ok(res !== null);
|
|
assert.equal(res.status, 200);
|
|
assert.equal(executedStages.length, 2);
|
|
assert.equal(executedStages[0], `${provider1}/model-1@${p1Conn.id}`);
|
|
assert.equal(executedStages[1], `${provider2}/model-2@${p2Conn.id}`);
|
|
});
|