chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
// tests/unit/combo/auto-quota-cutoff.test.ts
|
||||
// Regression coverage for auto routing quota cutoff: hard-cutoff candidates must be
|
||||
// removed before scoring/fallback so an exhausted account cannot win by latency/model fit.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { scoreAutoTargets } from "../../../open-sse/services/combo/autoStrategy.ts";
|
||||
import { getConnectionStatusQuotaCutoffReason } from "../../../open-sse/services/combo.ts";
|
||||
import type {
|
||||
AutoProviderCandidate,
|
||||
ResolvedComboTarget,
|
||||
} from "../../../open-sse/services/combo/types.ts";
|
||||
import type { ScoringWeights } from "../../../open-sse/services/autoCombo/scoring.ts";
|
||||
|
||||
const latencyOnlyWeights: ScoringWeights = {
|
||||
quota: 0,
|
||||
health: 0,
|
||||
costInv: 0,
|
||||
latencyInv: 1,
|
||||
taskFit: 0,
|
||||
stability: 0,
|
||||
tierPriority: 0,
|
||||
tierAffinity: 0,
|
||||
specificityMatch: 0,
|
||||
contextAffinity: 0,
|
||||
resetWindowAffinity: 0,
|
||||
connectionDensity: 0,
|
||||
};
|
||||
|
||||
function target(provider: string, model: string, connectionId: string): ResolvedComboTarget {
|
||||
return {
|
||||
kind: "model",
|
||||
stepId: `${provider}-${model}-${connectionId}`,
|
||||
executionKey: `${provider}/${model}@${connectionId}`,
|
||||
modelStr: `${provider}/${model}`,
|
||||
provider,
|
||||
providerId: null,
|
||||
connectionId,
|
||||
} as ResolvedComboTarget;
|
||||
}
|
||||
|
||||
function candidate(
|
||||
provider: string,
|
||||
model: string,
|
||||
connectionId: string,
|
||||
overrides: Partial<AutoProviderCandidate> = {}
|
||||
): AutoProviderCandidate {
|
||||
return {
|
||||
provider,
|
||||
model,
|
||||
stepId: `${provider}-${model}-${connectionId}`,
|
||||
executionKey: `${provider}/${model}@${connectionId}`,
|
||||
modelStr: `${provider}/${model}`,
|
||||
connectionId,
|
||||
quotaRemaining: 100,
|
||||
quotaTotal: 100,
|
||||
circuitBreakerState: "CLOSED",
|
||||
costPer1MTokens: 1,
|
||||
p95LatencyMs: 1000,
|
||||
latencyStdDev: 10,
|
||||
errorRate: 0,
|
||||
resetWindowAffinity: 0.5,
|
||||
connectionPoolSize: 1,
|
||||
...overrides,
|
||||
} as AutoProviderCandidate;
|
||||
}
|
||||
|
||||
test("auto scoring skips GLM when its 2% remaining quota hit the hard cutoff", () => {
|
||||
const targets = [target("glm", "glm-5.2", "glm-empty"), target("mcode", "mimo-auto", "mcode-ok")];
|
||||
const ranked = scoreAutoTargets(
|
||||
targets,
|
||||
[
|
||||
candidate("glm", "glm-5.2", "glm-empty", {
|
||||
quotaRemaining: 2,
|
||||
p95LatencyMs: 10,
|
||||
quotaCutoffBlocked: true,
|
||||
quotaCutoffReason: "quota_exhausted",
|
||||
}),
|
||||
candidate("mcode", "mimo-auto", "mcode-ok", {
|
||||
quotaRemaining: 100,
|
||||
p95LatencyMs: 5000,
|
||||
}),
|
||||
],
|
||||
"coding",
|
||||
latencyOnlyWeights
|
||||
);
|
||||
|
||||
assert.equal(ranked.length, 1);
|
||||
assert.equal(ranked[0]?.target.provider, "mcode");
|
||||
assert.equal(ranked[0]?.target.connectionId, "mcode-ok");
|
||||
});
|
||||
|
||||
test("blocked quota candidates are not included in the scoring pool", () => {
|
||||
const targets = [
|
||||
target("fast", "healthy", "fast-ok"),
|
||||
target("slow", "healthy", "slow-ok"),
|
||||
target("glm", "glm-5.2", "glm-empty"),
|
||||
];
|
||||
const ranked = scoreAutoTargets(
|
||||
targets,
|
||||
[
|
||||
candidate("fast", "healthy", "fast-ok", { p95LatencyMs: 100 }),
|
||||
candidate("slow", "healthy", "slow-ok", { p95LatencyMs: 1000 }),
|
||||
candidate("glm", "glm-5.2", "glm-empty", {
|
||||
quotaRemaining: 0,
|
||||
p95LatencyMs: 10000,
|
||||
quotaCutoffBlocked: true,
|
||||
quotaCutoffReason: "quota_exhausted",
|
||||
}),
|
||||
],
|
||||
"coding",
|
||||
latencyOnlyWeights
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
ranked.map((entry) => entry.target.provider),
|
||||
["fast", "slow"]
|
||||
);
|
||||
const slowScore = ranked.find((entry) => entry.target.provider === "slow")?.score;
|
||||
assert.equal(
|
||||
slowScore,
|
||||
0,
|
||||
"the blocked GLM latency must not inflate surviving candidates' scores"
|
||||
);
|
||||
});
|
||||
|
||||
test("connection terminal status maps to quota cutoff reason", () => {
|
||||
assert.equal(
|
||||
getConnectionStatusQuotaCutoffReason({ testStatus: "credits_exhausted" }),
|
||||
"credits_exhausted"
|
||||
);
|
||||
assert.equal(getConnectionStatusQuotaCutoffReason({ testStatus: "expired" }), "expired");
|
||||
assert.equal(getConnectionStatusQuotaCutoffReason({ testStatus: "active" }), undefined);
|
||||
});
|
||||
|
||||
test("future unavailable connection maps to rate_limited quota cutoff reason", () => {
|
||||
assert.equal(
|
||||
getConnectionStatusQuotaCutoffReason({
|
||||
testStatus: "unavailable",
|
||||
rateLimitedUntil: new Date(Date.now() + 60_000).toISOString(),
|
||||
}),
|
||||
"rate_limited"
|
||||
);
|
||||
assert.equal(
|
||||
getConnectionStatusQuotaCutoffReason({
|
||||
testStatus: "unavailable",
|
||||
rateLimitedUntil: new Date(Date.now() - 60_000).toISOString(),
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
test("status-blocked candidates are removed before auto scoring", () => {
|
||||
const targets = [
|
||||
target("puter", "fast-free", "puter-empty"),
|
||||
target("cerebras", "healthy", "cerebras-ok"),
|
||||
];
|
||||
const ranked = scoreAutoTargets(
|
||||
targets,
|
||||
[
|
||||
candidate("puter", "fast-free", "puter-empty", {
|
||||
quotaRemaining: 0,
|
||||
p95LatencyMs: 5,
|
||||
quotaCutoffBlocked: true,
|
||||
quotaCutoffReason: "credits_exhausted",
|
||||
}),
|
||||
candidate("cerebras", "healthy", "cerebras-ok", {
|
||||
quotaRemaining: 100,
|
||||
p95LatencyMs: 5000,
|
||||
}),
|
||||
],
|
||||
"coding",
|
||||
latencyOnlyWeights
|
||||
);
|
||||
|
||||
assert.equal(ranked.length, 1);
|
||||
assert.equal(ranked[0]?.target.provider, "cerebras");
|
||||
});
|
||||
|
||||
// --- Maintainer gate (#4592 review): terminal-status cutoff is opt-in (#4483) ---
|
||||
// buildAutoCandidates only marks a terminal-status connection as quotaCutoffBlocked
|
||||
// when the quota-cutoff opt-in is enabled. With the opt-in OFF, the connection must
|
||||
// fall through to normal connection-cooldown / model-lockout handling instead of being
|
||||
// hard-excluded here (which would surface a misleading "below quota cutoff" 429 when
|
||||
// every candidate is merely transiently unavailable). This locks the gating expression
|
||||
// `quotaCutoffEnabled ? getConnectionStatusQuotaCutoffReason(conn) : undefined`
|
||||
// against accidental removal.
|
||||
test("terminal-status cutoff is consulted only when quota cutoff is enabled", () => {
|
||||
const terminalConn = { testStatus: "credits_exhausted" };
|
||||
|
||||
// Helper itself still classifies the terminal status (unchanged).
|
||||
assert.equal(getConnectionStatusQuotaCutoffReason(terminalConn), "credits_exhausted");
|
||||
|
||||
// The gate: enabled → reason flows through; disabled → suppressed (fall-through).
|
||||
const gated = (enabled: boolean) =>
|
||||
enabled ? getConnectionStatusQuotaCutoffReason(terminalConn) : undefined;
|
||||
|
||||
assert.equal(gated(true), "credits_exhausted", "enabled: terminal status blocks the candidate");
|
||||
assert.equal(gated(false), undefined, "disabled: terminal status must NOT pre-block (opt-in)");
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
// tests/unit/combo/auto-status-penalty-4540.test.ts
|
||||
// Regression for #4540: an exhausted provider connection (e.g. credits_exhausted /
|
||||
// rate_limited with no numeric quota fetcher) used to score IDENTICALLY to a healthy
|
||||
// one — quotaRemaining defaulted to 100 and testStatus never entered scoring — so auto
|
||||
// routing kept picking dead providers. With the quota-preflight hard cutoff DISABLED
|
||||
// (the default), we must NOT hard-block such a candidate (that would surface a misleading
|
||||
// "below quota cutoff" 429); instead a SOFT status penalty must rank it strictly BELOW
|
||||
// an otherwise-identical healthy candidate.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { scoreAutoTargets } from "../../../open-sse/services/combo/autoStrategy.ts";
|
||||
import type {
|
||||
AutoProviderCandidate,
|
||||
ResolvedComboTarget,
|
||||
} from "../../../open-sse/services/combo/types.ts";
|
||||
import type { ScoringWeights } from "../../../open-sse/services/autoCombo/scoring.ts";
|
||||
|
||||
// Quota-weighted only so the two candidates would otherwise tie at quotaRemaining=100.
|
||||
const quotaOnlyWeights: ScoringWeights = {
|
||||
quota: 1,
|
||||
health: 0,
|
||||
costInv: 0,
|
||||
latencyInv: 0,
|
||||
taskFit: 0,
|
||||
stability: 0,
|
||||
tierPriority: 0,
|
||||
tierAffinity: 0,
|
||||
specificityMatch: 0,
|
||||
contextAffinity: 0,
|
||||
resetWindowAffinity: 0,
|
||||
connectionDensity: 0,
|
||||
};
|
||||
|
||||
function target(provider: string, model: string, connectionId: string): ResolvedComboTarget {
|
||||
return {
|
||||
kind: "model",
|
||||
stepId: `${provider}-${model}-${connectionId}`,
|
||||
executionKey: `${provider}/${model}@${connectionId}`,
|
||||
modelStr: `${provider}/${model}`,
|
||||
provider,
|
||||
providerId: null,
|
||||
connectionId,
|
||||
} as ResolvedComboTarget;
|
||||
}
|
||||
|
||||
function candidate(
|
||||
provider: string,
|
||||
model: string,
|
||||
connectionId: string,
|
||||
overrides: Partial<AutoProviderCandidate> = {}
|
||||
): AutoProviderCandidate {
|
||||
return {
|
||||
provider,
|
||||
model,
|
||||
stepId: `${provider}-${model}-${connectionId}`,
|
||||
executionKey: `${provider}/${model}@${connectionId}`,
|
||||
modelStr: `${provider}/${model}`,
|
||||
connectionId,
|
||||
quotaRemaining: 100,
|
||||
quotaTotal: 100,
|
||||
circuitBreakerState: "CLOSED",
|
||||
costPer1MTokens: 1,
|
||||
p95LatencyMs: 1000,
|
||||
latencyStdDev: 10,
|
||||
errorRate: 0,
|
||||
resetWindowAffinity: 0.5,
|
||||
connectionPoolSize: 1,
|
||||
...overrides,
|
||||
} as AutoProviderCandidate;
|
||||
}
|
||||
|
||||
test("#4540: exhausted (statusPenalty) candidate scores strictly BELOW an identical healthy one without being hard-blocked", () => {
|
||||
const targets = [target("dead", "m", "dead-conn"), target("healthy", "m", "healthy-conn")];
|
||||
const ranked = scoreAutoTargets(
|
||||
targets,
|
||||
[
|
||||
// Exhausted connection with no numeric quota fetcher: quotaRemaining stays 100,
|
||||
// but the connection terminal status flags a soft penalty (NOT a hard block).
|
||||
candidate("dead", "m", "dead-conn", { statusPenalty: true }),
|
||||
candidate("healthy", "m", "healthy-conn"),
|
||||
],
|
||||
"coding",
|
||||
quotaOnlyWeights
|
||||
);
|
||||
|
||||
// Soft penalty: NOT hard-blocked — both candidates are still in the pool.
|
||||
assert.equal(ranked.length, 2, "soft penalty must not drop the exhausted candidate");
|
||||
|
||||
const dead = ranked.find((e) => e.target.provider === "dead");
|
||||
const healthy = ranked.find((e) => e.target.provider === "healthy");
|
||||
assert.ok(dead && healthy, "both candidates present");
|
||||
|
||||
// The fix: exhausted scores STRICTLY less than the identical healthy one (was a tie).
|
||||
assert.ok(
|
||||
(dead!.score as number) < (healthy!.score as number),
|
||||
`exhausted score (${dead!.score}) must be strictly < healthy score (${healthy!.score})`
|
||||
);
|
||||
|
||||
// And ranking puts healthy first.
|
||||
assert.equal(ranked[0]?.target.provider, "healthy");
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
// tests/unit/combo/combo-context.test.ts
|
||||
// Unit tests for the first extracted combo phase (god-file decomposition fase 1):
|
||||
// createComboContext (combo/context.ts) + phaseComboSetup (combo/comboSetup.ts).
|
||||
// The pinning-ON path (getLastSessionModel) is covered end-to-end by the existing combo
|
||||
// characterization suite (combo-sessionless-pin-3825, combo-config, etc.); here we exercise
|
||||
// the pure path (context_cache_protection OFF, no settings -> no DB) and the body carrier.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createComboContext } from "../../../open-sse/services/combo/context.ts";
|
||||
import { phaseComboSetup } from "../../../open-sse/services/combo/comboSetup.ts";
|
||||
|
||||
const log = { info() {}, warn() {}, error() {}, debug() {} };
|
||||
|
||||
test("createComboContext carries inputs and the body BY REFERENCE", () => {
|
||||
const body = { model: "auto", messages: [], stream: true };
|
||||
const combo = { name: "c1", models: ["a", "b"] };
|
||||
const ctx = createComboContext({ body, combo, log });
|
||||
assert.equal(ctx.body, body, "body must be the same reference (not copied) for byte-identical pinning");
|
||||
assert.equal(ctx.combo, combo);
|
||||
assert.equal(ctx.settings, null);
|
||||
assert.equal(ctx.relayOptions, null);
|
||||
assert.equal(ctx.log, log);
|
||||
});
|
||||
|
||||
test("phaseComboSetup resolves strategy/config/stream with pinning OFF (pure, no DB)", () => {
|
||||
const body = { model: "auto", messages: [], stream: true };
|
||||
const combo = { name: "c1", models: ["a"], strategy: "priority" };
|
||||
const ctx = createComboContext({ body, combo, log });
|
||||
|
||||
const setup = phaseComboSetup(ctx);
|
||||
|
||||
assert.equal(setup.strategy, "priority");
|
||||
assert.equal(setup.pinnedModel, null, "no pin when context_cache_protection is off");
|
||||
assert.equal(setup.effectiveSessionId, null);
|
||||
assert.equal(setup.clientRequestedStream, true, "body.stream === true");
|
||||
assert.equal(typeof setup.comboTargetTimeoutMs, "number");
|
||||
assert.equal(typeof setup.reasoningTokenBufferEnabled, "boolean");
|
||||
assert.ok(setup.config && typeof setup.config === "object", "config cascade resolved");
|
||||
assert.ok(
|
||||
setup.resilienceSettings && typeof setup.resilienceSettings === "object",
|
||||
"resilience settings resolved"
|
||||
);
|
||||
});
|
||||
|
||||
test("phaseComboSetup: clientRequestedStream is false when body.stream is not true", () => {
|
||||
const ctx = createComboContext({
|
||||
body: { model: "auto", messages: [] },
|
||||
combo: { name: "c", models: ["a"] },
|
||||
log,
|
||||
});
|
||||
const setup = phaseComboSetup(ctx);
|
||||
assert.equal(setup.clientRequestedStream, false);
|
||||
});
|
||||
|
||||
test("phaseComboSetup normalizes an unknown strategy to a valid routing strategy", () => {
|
||||
const ctx = createComboContext({
|
||||
body: { model: "auto", messages: [] },
|
||||
combo: { name: "c", models: ["a"], strategy: "not-a-real-strategy" },
|
||||
log,
|
||||
});
|
||||
const setup = phaseComboSetup(ctx);
|
||||
// normalizeRoutingStrategy falls back to the default for unknown values.
|
||||
assert.equal(typeof setup.strategy, "string");
|
||||
assert.ok(setup.strategy.length > 0);
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
// tests/unit/combo/combo-exhausted-skip.test.ts
|
||||
// Characterization of getExhaustedTargetSkipReason — the de-duplicated #1731/#1731v2
|
||||
// pre-dispatch skip predicate shared by both combo dispatchers (handleComboChat +
|
||||
// handleRoundRobinCombo). Locks the exact skip conditions + message strings.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { getExhaustedTargetSkipReason } from "../../../open-sse/services/combo/comboPredicates.ts";
|
||||
|
||||
function target(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
kind: "model",
|
||||
executionKey: "ek",
|
||||
modelStr: "openai/gpt-4o",
|
||||
provider: "openai",
|
||||
providerId: null,
|
||||
connectionId: "conn-1",
|
||||
...overrides,
|
||||
} as Parameters<typeof getExhaustedTargetSkipReason>[0];
|
||||
}
|
||||
|
||||
test("returns null when nothing is exhausted", () => {
|
||||
assert.equal(getExhaustedTargetSkipReason(target(), new Set(), new Set()), null);
|
||||
});
|
||||
|
||||
test("#1731v2: skips when the provider:connection pair is in exhaustedConnections", () => {
|
||||
const reason = getExhaustedTargetSkipReason(target(), new Set(), new Set(["openai:conn-1"]));
|
||||
assert.equal(
|
||||
reason,
|
||||
"Skipping openai/gpt-4o — connection conn-1 for provider openai had connection error (#1731v2)"
|
||||
);
|
||||
});
|
||||
|
||||
test("#1731: skips when the provider is in exhaustedProviders", () => {
|
||||
const reason = getExhaustedTargetSkipReason(target(), new Set(["openai"]), new Set());
|
||||
assert.equal(
|
||||
reason,
|
||||
"Skipping openai/gpt-4o — provider openai marked exhausted this request (#1731)"
|
||||
);
|
||||
});
|
||||
|
||||
test("connection exhaustion takes precedence over provider exhaustion", () => {
|
||||
const reason = getExhaustedTargetSkipReason(
|
||||
target(),
|
||||
new Set(["openai"]),
|
||||
new Set(["openai:conn-1"])
|
||||
);
|
||||
assert.ok(reason?.includes("(#1731v2)"));
|
||||
});
|
||||
|
||||
test("a different connection of an exhausted pair is NOT skipped on the connection check", () => {
|
||||
const reason = getExhaustedTargetSkipReason(
|
||||
target({ connectionId: "conn-2" }),
|
||||
new Set(),
|
||||
new Set(["openai:conn-1"])
|
||||
);
|
||||
assert.equal(reason, null);
|
||||
});
|
||||
|
||||
test("no connectionId: only the provider-level check applies", () => {
|
||||
assert.equal(getExhaustedTargetSkipReason(target({ connectionId: null }), new Set(), new Set()), null);
|
||||
assert.ok(
|
||||
getExhaustedTargetSkipReason(target({ connectionId: null }), new Set(["openai"]), new Set())?.includes(
|
||||
"(#1731)"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("empty provider string is treated as falsy (no skip)", () => {
|
||||
assert.equal(
|
||||
getExhaustedTargetSkipReason(target({ provider: "" }), new Set([""]), new Set([":conn-1"])),
|
||||
null
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,365 @@
|
||||
// tests/unit/combo/combo-target-exhaustion.test.ts
|
||||
// Characterization of applyComboTargetExhaustion — the de-duplicated #1731/#1731v2 upstream-error
|
||||
// → exhaustion-set classification shared by both combo dispatchers. Locks the SET mutations
|
||||
// (which drive same-request target skipping) and the providerExhausted return.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
applyComboTargetExhaustion,
|
||||
type ComboExhaustionSets,
|
||||
} from "../../../open-sse/services/combo/targetExhaustion.ts";
|
||||
|
||||
const log = { info() {}, warn() {}, error() {}, debug() {} };
|
||||
|
||||
function sets(): ComboExhaustionSets {
|
||||
return {
|
||||
exhaustedProviders: new Set<string>(),
|
||||
exhaustedConnections: new Set<string>(),
|
||||
transientRateLimitedProviders: new Set<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
function target(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
kind: "model",
|
||||
executionKey: "ek",
|
||||
modelStr: "test-dedup-provider/m1",
|
||||
provider: "test-dedup-provider",
|
||||
providerId: null,
|
||||
connectionId: "conn-1",
|
||||
...overrides,
|
||||
} as Parameters<typeof applyComboTargetExhaustion>[0];
|
||||
}
|
||||
|
||||
const baseOpts = {
|
||||
errorText: "plain upstream error",
|
||||
rawModel: "m1",
|
||||
isTokenLimitBreach: false,
|
||||
allAccountsRateLimited: false,
|
||||
log,
|
||||
tag: "COMBO",
|
||||
exhaustedLogLevel: "info" as const,
|
||||
};
|
||||
|
||||
test("marks provider exhausted when the fallback result signals quota exhaustion", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 429 },
|
||||
fallbackResult: { creditsExhausted: true },
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, true);
|
||||
assert.ok(s.exhaustedProviders.has("test-dedup-provider"));
|
||||
assert.equal(s.transientRateLimitedProviders.size, 0);
|
||||
});
|
||||
|
||||
test("round-robin's allAccountsRateLimited term also marks the provider exhausted", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 503 },
|
||||
fallbackResult: {},
|
||||
allAccountsRateLimited: true,
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, true);
|
||||
assert.ok(s.exhaustedProviders.has("test-dedup-provider"));
|
||||
});
|
||||
|
||||
test("a transient 429 (not exhausted) marks the provider rate-limited, not exhausted", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 429 },
|
||||
fallbackResult: {},
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
assert.ok(s.transientRateLimitedProviders.has("test-dedup-provider"));
|
||||
assert.equal(s.exhaustedProviders.size, 0);
|
||||
});
|
||||
|
||||
test("connection-level 5xx with a connectionId poisons exhaustedConnections (#1731v2)", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 502, headers: null },
|
||||
fallbackResult: {},
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
assert.ok(s.exhaustedConnections.has("test-dedup-provider:conn-1"));
|
||||
assert.equal(s.exhaustedProviders.size, 0);
|
||||
});
|
||||
|
||||
test("connection-level 5xx without a connectionId poisons exhaustedProviders (#1731)", () => {
|
||||
const s = sets();
|
||||
applyComboTargetExhaustion(target({ connectionId: null }), {
|
||||
...baseOpts,
|
||||
result: { status: 503, headers: null },
|
||||
fallbackResult: {},
|
||||
sets: s,
|
||||
});
|
||||
assert.ok(s.exhaustedProviders.has("test-dedup-provider"));
|
||||
assert.equal(s.exhaustedConnections.size, 0);
|
||||
});
|
||||
|
||||
test("an unknown provider is never marked (guard)", () => {
|
||||
const s = sets();
|
||||
applyComboTargetExhaustion(target({ provider: "unknown" }), {
|
||||
...baseOpts,
|
||||
result: { status: 502, headers: null },
|
||||
fallbackResult: { creditsExhausted: true },
|
||||
allAccountsRateLimited: true,
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(s.exhaustedProviders.size, 0);
|
||||
assert.equal(s.exhaustedConnections.size, 0);
|
||||
});
|
||||
|
||||
test("structuredError.code takes precedence over raw errorText for exhaustion classification", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
errorText: "Resource has been exhausted (e.g. check quota).",
|
||||
result: { status: 429 },
|
||||
fallbackResult: {},
|
||||
sets: s,
|
||||
structuredError: { code: "rate_limit_exceeded" },
|
||||
});
|
||||
assert.equal(exhausted, false, "rate_limit_exceeded should NOT mark provider exhausted");
|
||||
assert.ok(
|
||||
s.transientRateLimitedProviders.has("test-dedup-provider"),
|
||||
"should be in transientRateLimitedProviders"
|
||||
);
|
||||
});
|
||||
|
||||
test("structuredError.code with non-matching value falls back to classifyErrorText behavior", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
errorText: "Rate limit reached",
|
||||
result: { status: 429 },
|
||||
fallbackResult: {},
|
||||
sets: s,
|
||||
structuredError: { code: "some_unknown_code" },
|
||||
});
|
||||
assert.equal(exhausted, false, "unknown code + non-quota errorText → not exhausted");
|
||||
assert.ok(
|
||||
s.transientRateLimitedProviders.has("test-dedup-provider"),
|
||||
"should be in transientRateLimitedProviders"
|
||||
);
|
||||
});
|
||||
|
||||
test("a 200/benign status with no exhaustion mutates nothing and returns false", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 200 },
|
||||
fallbackResult: {},
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
assert.equal(
|
||||
s.exhaustedProviders.size + s.exhaustedConnections.size + s.transientRateLimitedProviders.size,
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
test("does NOT mark provider exhausted for per-model-quota providers (different model)", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target({ provider: "gemini" }), {
|
||||
...baseOpts,
|
||||
result: { status: 429 },
|
||||
fallbackResult: { reason: "quota_exhausted" },
|
||||
errorText: "quota exceeded for model gpt-4",
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
assert.equal(s.exhaustedProviders.has("gemini"), false);
|
||||
assert.ok(s.transientRateLimitedProviders.has("gemini"));
|
||||
});
|
||||
|
||||
test("does NOT mark provider exhausted for empty provider strings", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target({ provider: "" }), {
|
||||
...baseOpts,
|
||||
result: { status: 503 },
|
||||
fallbackResult: { error: { code: "quota_exhausted" } },
|
||||
errorText: "quota exhausted",
|
||||
allAccountsRateLimited: true,
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
});
|
||||
|
||||
test("does NOT mark transientRateLimited on 429 when isTokenLimitBreach is true", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 429 },
|
||||
fallbackResult: {},
|
||||
errorText: "Token limit exceeded",
|
||||
isTokenLimitBreach: true,
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
assert.equal(s.transientRateLimitedProviders.has("test-dedup-provider"), false);
|
||||
assert.equal(s.exhaustedProviders.has("test-dedup-provider"), false);
|
||||
});
|
||||
|
||||
test("does NOT mark anything for circuit-open (X-OmniRoute-Provider-Breaker header)", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 503, headers: new Map([["x-omniroute-provider-breaker", "open"]]) },
|
||||
fallbackResult: {},
|
||||
errorText: "",
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
assert.equal(s.exhaustedProviders.has("test-dedup-provider"), false);
|
||||
assert.equal(s.exhaustedConnections.has("test-dedup-provider:conn-1"), false);
|
||||
assert.equal(s.transientRateLimitedProviders.has("test-dedup-provider"), false);
|
||||
});
|
||||
|
||||
test("does NOT mark exhaustion for non-connection-level status codes (400)", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(target(), {
|
||||
...baseOpts,
|
||||
result: { status: 400 },
|
||||
fallbackResult: {},
|
||||
errorText: "Bad Request",
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(exhausted, false);
|
||||
assert.equal(s.exhaustedConnections.size, 0);
|
||||
assert.equal(s.exhaustedProviders.size, 0);
|
||||
assert.equal(s.transientRateLimitedProviders.size, 0);
|
||||
});
|
||||
|
||||
test("does NOT mark connection exhausted for per-model-quota provider on 500 (gemini model-level error)", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(
|
||||
target({ provider: "gemini", connectionId: "gemini-conn-1" }),
|
||||
{
|
||||
...baseOpts,
|
||||
result: { status: 500 },
|
||||
fallbackResult: {},
|
||||
errorText: "Internal error encountered.",
|
||||
rawModel: "gemma-4-31b-it",
|
||||
sets: s,
|
||||
}
|
||||
);
|
||||
assert.equal(exhausted, false);
|
||||
assert.equal(s.exhaustedProviders.has("gemini"), false);
|
||||
assert.equal(s.exhaustedConnections.has("gemini:gemini-conn-1"), false);
|
||||
assert.equal(s.transientRateLimitedProviders.has("gemini"), false);
|
||||
});
|
||||
|
||||
// Sanitized Gemini 500 response — model-level "Internal error encountered" should NOT exhaust
|
||||
// the connection, allowing sibling models on the same provider to be tried.
|
||||
test("gemini 500 INTERNAL (sanitized real response) does NOT exhaust connection — sibling retry", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(
|
||||
target({ provider: "gemini", connectionId: "gemini-key-abc" }),
|
||||
{
|
||||
...baseOpts,
|
||||
result: { status: 500 },
|
||||
fallbackResult: {},
|
||||
errorText: "Internal error encountered.",
|
||||
rawModel: "gemma-4-31b-it",
|
||||
structuredError: { code: 500, status: "INTERNAL", message: "Internal error encountered." },
|
||||
sets: s,
|
||||
}
|
||||
);
|
||||
assert.equal(exhausted, false, "providerExhausted must be false");
|
||||
assert.equal(s.exhaustedProviders.has("gemini"), false, "must not exhaust provider");
|
||||
assert.equal(
|
||||
s.exhaustedConnections.has("gemini:gemini-key-abc"),
|
||||
false,
|
||||
"must not exhaust connection — sibling model may succeed"
|
||||
);
|
||||
assert.equal(s.transientRateLimitedProviders.has("gemini"), false);
|
||||
});
|
||||
|
||||
// Non-500 connection-level errors MUST exhaust the connection even for per-model-quota providers.
|
||||
// A 503 (Service Unavailable) means the upstream is down — retrying sibling models wastes calls.
|
||||
test("gemini 503 DOES exhaust connection (upstream down, not model-level)", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(
|
||||
target({ provider: "gemini", connectionId: "gemini-key-abc" }),
|
||||
{
|
||||
...baseOpts,
|
||||
result: { status: 503 },
|
||||
fallbackResult: {},
|
||||
errorText: "The service is currently unavailable.",
|
||||
rawModel: "gemma-4-31b-it",
|
||||
sets: s,
|
||||
}
|
||||
);
|
||||
assert.equal(exhausted, false, "providerExhausted is false (not quota)");
|
||||
assert.equal(
|
||||
s.exhaustedConnections.has("gemini:gemini-key-abc"),
|
||||
true,
|
||||
"503 must exhaust connection — upstream is down"
|
||||
);
|
||||
assert.equal(s.exhaustedProviders.size, 0);
|
||||
});
|
||||
|
||||
test("gemini 502 DOES exhaust connection (bad gateway)", () => {
|
||||
const s = sets();
|
||||
const exhausted = applyComboTargetExhaustion(
|
||||
target({ provider: "gemini", connectionId: "gemini-key-abc" }),
|
||||
{
|
||||
...baseOpts,
|
||||
result: { status: 502 },
|
||||
fallbackResult: {},
|
||||
errorText: "Bad Gateway",
|
||||
rawModel: "gemma-4-31b-it",
|
||||
sets: s,
|
||||
}
|
||||
);
|
||||
assert.equal(exhausted, false);
|
||||
assert.equal(s.exhaustedConnections.has("gemini:gemini-key-abc"), true);
|
||||
});
|
||||
|
||||
test("gemini 504 DOES exhaust connection (gateway timeout)", () => {
|
||||
const s = sets();
|
||||
applyComboTargetExhaustion(target({ provider: "gemini", connectionId: "gemini-key-abc" }), {
|
||||
...baseOpts,
|
||||
result: { status: 504 },
|
||||
fallbackResult: {},
|
||||
errorText: "Gateway Timeout",
|
||||
rawModel: "gemini-2.0-flash",
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(s.exhaustedConnections.has("gemini:gemini-key-abc"), true);
|
||||
});
|
||||
|
||||
test("gemini 408 DOES exhaust connection (request timeout)", () => {
|
||||
const s = sets();
|
||||
applyComboTargetExhaustion(target({ provider: "gemini", connectionId: "gemini-key-abc" }), {
|
||||
...baseOpts,
|
||||
result: { status: 408 },
|
||||
fallbackResult: {},
|
||||
errorText: "Request Timeout",
|
||||
rawModel: "gemini-2.0-flash",
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(s.exhaustedConnections.has("gemini:gemini-key-abc"), true);
|
||||
});
|
||||
|
||||
test("gemini 524 DOES exhaust connection (cloudflare timeout)", () => {
|
||||
const s = sets();
|
||||
applyComboTargetExhaustion(target({ provider: "gemini", connectionId: "gemini-key-abc" }), {
|
||||
...baseOpts,
|
||||
result: { status: 524 },
|
||||
fallbackResult: {},
|
||||
errorText: "A Timeout Occurred",
|
||||
rawModel: "gemini-2.0-flash",
|
||||
sets: s,
|
||||
});
|
||||
assert.equal(s.exhaustedConnections.has("gemini:gemini-key-abc"), true);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
// tests/unit/combo/effective-max-concurrency.test.ts
|
||||
// Unit test for effectiveMaxConcurrency — the pure resolver that turns a
|
||||
// connection's per-account concurrency cap (provider_connections.max_concurrent)
|
||||
// into the semaphore's maxConcurrency for a round-robin combo target, falling
|
||||
// back to the combo-level default when the connection has no positive cap.
|
||||
//
|
||||
// Runner: node:test + assert/strict (matches the sibling combo predicate tests).
|
||||
import { test, describe } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { effectiveMaxConcurrency } from "../../../open-sse/services/combo/comboPredicates.ts";
|
||||
|
||||
describe("effectiveMaxConcurrency", () => {
|
||||
test("a positive per-connection cap wins over the fallback", () => {
|
||||
assert.equal(effectiveMaxConcurrency(1, 3), 1, "GLM/MiniMax-style cap of 1 must be honored");
|
||||
assert.equal(effectiveMaxConcurrency(5, 3), 5, "a cap higher than the default is still used");
|
||||
});
|
||||
|
||||
test("null cap → fallback (no per-connection limit configured)", () => {
|
||||
assert.equal(effectiveMaxConcurrency(null, 3), 3);
|
||||
});
|
||||
|
||||
test("undefined cap → fallback", () => {
|
||||
assert.equal(effectiveMaxConcurrency(undefined, 7), 7);
|
||||
});
|
||||
|
||||
test("zero cap → fallback (0 means 'no limit', not 'block everything')", () => {
|
||||
assert.equal(effectiveMaxConcurrency(0, 3), 3);
|
||||
});
|
||||
|
||||
test("negative cap → fallback (defensive)", () => {
|
||||
assert.equal(effectiveMaxConcurrency(-2, 4), 4);
|
||||
});
|
||||
|
||||
test("non-finite cap → fallback (defensive)", () => {
|
||||
assert.equal(effectiveMaxConcurrency(Number.NaN, 4), 4);
|
||||
assert.equal(effectiveMaxConcurrency(Number.POSITIVE_INFINITY, 4), 4);
|
||||
});
|
||||
|
||||
test("a non-integer positive cap is floored to a whole slot count", () => {
|
||||
assert.equal(
|
||||
effectiveMaxConcurrency(2.9, 3),
|
||||
2,
|
||||
"fractional caps floor to whole concurrency slots"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* tests/unit/combo/quota-share-concurrency.test.ts
|
||||
*
|
||||
* FASE 2.1: the per-connection concurrency slot for quota-share combos. These
|
||||
* tests pin the contract of acquireQuotaShareConcurrencySlot against the real
|
||||
* semaphore module: no limit when there is no cap, a stable connection-scoped
|
||||
* key, genuine serialization (a second request WAITS until the first releases),
|
||||
* and fail-open behavior when the queue is saturated.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import * as semaphore from "../../../open-sse/services/rateLimitSemaphore.ts";
|
||||
import {
|
||||
quotaShareConcurrencyKey,
|
||||
acquireQuotaShareConcurrencySlot,
|
||||
} from "../../../open-sse/services/combo/quotaShareConcurrency.ts";
|
||||
|
||||
const noopLog = { warn: () => {} };
|
||||
|
||||
function target(connectionId: string) {
|
||||
return {
|
||||
connectionId,
|
||||
modelStr: "p/m",
|
||||
executionKey: "p/m",
|
||||
provider: "p",
|
||||
stepId: "s",
|
||||
label: "p/m",
|
||||
} as never;
|
||||
}
|
||||
|
||||
const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
test("quotaShareConcurrencyKey is stable and connection-scoped", () => {
|
||||
assert.equal(quotaShareConcurrencyKey("abc"), "qsconn:abc");
|
||||
assert.equal(quotaShareConcurrencyKey("abc"), quotaShareConcurrencyKey("abc"));
|
||||
assert.notEqual(quotaShareConcurrencyKey("a"), quotaShareConcurrencyKey("b"));
|
||||
});
|
||||
|
||||
test("no slot when cap is null (no per-connection limit → unchanged behavior)", async () => {
|
||||
semaphore.resetAll();
|
||||
const release = await acquireQuotaShareConcurrencySlot(
|
||||
target("c1"),
|
||||
null,
|
||||
{ queueTimeoutMs: 50, maxQueueSize: 10 },
|
||||
noopLog
|
||||
);
|
||||
assert.equal(release, null);
|
||||
});
|
||||
|
||||
test("no slot when cap <= 0", async () => {
|
||||
semaphore.resetAll();
|
||||
assert.equal(
|
||||
await acquireQuotaShareConcurrencySlot(
|
||||
target("c1"),
|
||||
0,
|
||||
{ queueTimeoutMs: 50, maxQueueSize: 10 },
|
||||
noopLog
|
||||
),
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test("no slot when connectionId is empty", async () => {
|
||||
semaphore.resetAll();
|
||||
const release = await acquireQuotaShareConcurrencySlot(
|
||||
target(""),
|
||||
1,
|
||||
{ queueTimeoutMs: 50, maxQueueSize: 10 },
|
||||
noopLog
|
||||
);
|
||||
assert.equal(release, null);
|
||||
});
|
||||
|
||||
test("acquires a slot when a positive cap is set", async () => {
|
||||
semaphore.resetAll();
|
||||
const release = await acquireQuotaShareConcurrencySlot(
|
||||
target("c1"),
|
||||
1,
|
||||
{ queueTimeoutMs: 50, maxQueueSize: 10 },
|
||||
noopLog
|
||||
);
|
||||
assert.equal(typeof release, "function");
|
||||
release!();
|
||||
});
|
||||
|
||||
test("cap=1 serializes: a second concurrent request WAITS until the first releases", async () => {
|
||||
semaphore.resetAll();
|
||||
const opts = { queueTimeoutMs: 1000, maxQueueSize: 10 };
|
||||
const r1 = await acquireQuotaShareConcurrencySlot(target("c1"), 1, opts, noopLog);
|
||||
assert.equal(typeof r1, "function", "first request acquires the only slot");
|
||||
|
||||
let secondResolved = false;
|
||||
const p2 = acquireQuotaShareConcurrencySlot(target("c1"), 1, opts, noopLog).then((r) => {
|
||||
secondResolved = true;
|
||||
return r;
|
||||
});
|
||||
|
||||
await wait(60);
|
||||
assert.equal(
|
||||
secondResolved,
|
||||
false,
|
||||
"second request is still queued while the first holds the slot"
|
||||
);
|
||||
|
||||
r1!(); // release the first
|
||||
const r2 = await p2;
|
||||
assert.equal(secondResolved, true, "second request resolves only after the first releases");
|
||||
assert.equal(typeof r2, "function", "second request then acquires the freed slot");
|
||||
r2!();
|
||||
});
|
||||
|
||||
test("fail-open: a saturated queue proceeds without a slot (null), never blocks", async () => {
|
||||
semaphore.resetAll();
|
||||
const opts = { queueTimeoutMs: 1000, maxQueueSize: 0 };
|
||||
const r1 = await acquireQuotaShareConcurrencySlot(target("c1"), 1, opts, noopLog);
|
||||
assert.equal(typeof r1, "function", "first request acquires");
|
||||
const r2 = await acquireQuotaShareConcurrencySlot(target("c1"), 1, opts, noopLog);
|
||||
assert.equal(r2, null, "queue full → fail-open null (availability never worsened)");
|
||||
r1!();
|
||||
});
|
||||
|
||||
test("different connections have independent gates (no cross-contention)", async () => {
|
||||
semaphore.resetAll();
|
||||
const opts = { queueTimeoutMs: 50, maxQueueSize: 0 };
|
||||
const r1 = await acquireQuotaShareConcurrencySlot(target("c1"), 1, opts, noopLog);
|
||||
const r2 = await acquireQuotaShareConcurrencySlot(target("c2"), 1, opts, noopLog);
|
||||
assert.equal(typeof r1, "function");
|
||||
assert.equal(typeof r2, "function", "a different connection has its own slot");
|
||||
r1!();
|
||||
r2!();
|
||||
});
|
||||
Reference in New Issue
Block a user