mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
fix(tests): align test suite to post-#3355/#3366/#3399 behavior
- Remove 429 from PROVIDER_BREAKER_FAILURE_STATUSES; 429 belongs to connection cooldown, not whole-provider breaker (CLAUDE.md §resilience). PR #3366 correctly added 429 to PROVIDER_FAILURE_ERROR_CODES in accountFallback.ts (combo infinite-retry fix) but the parallel change to chat.ts was wrong — the integration test from v3.8.10 confirms this. - Align stream-utils tests to PR #3399 (SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT → "", message.content → null) and PR #3355 (malformed tool-call buffer now emitted as plain text, not suppressed). - Align services-branch-hardening test to PR #3399 (pinnedModel always null from applyComboAgentMiddleware; server-side session pinning replaced client-side <omniModel> tag extraction). - Align combo-routing-engine context-cache tests to PR #3399 (no <omniModel> tag in output, no X-OmniRoute-Model header, priority routing unchanged).
This commit is contained in:
@@ -173,7 +173,7 @@ function intersectAllowedConnectionIds(primary: unknown, secondary: unknown): st
|
||||
return first || second || null;
|
||||
}
|
||||
|
||||
const PROVIDER_BREAKER_FAILURE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
||||
const PROVIDER_BREAKER_FAILURE_STATUSES = new Set([408, 500, 502, 503, 504]);
|
||||
|
||||
/**
|
||||
* Handle chat completion request
|
||||
|
||||
@@ -2359,6 +2359,9 @@ test("handleComboChat auto strategy can route by SLA targets", async () => {
|
||||
});
|
||||
|
||||
test("handleComboChat context cache protection pins the model and tags tool-call responses", async () => {
|
||||
// PR #3399: <omniModel> tag extraction replaced by server-side session pinning.
|
||||
// combo uses priority order; the tag in the input message is stripped but no
|
||||
// longer drives routing. No <omniModel> tag is injected in responses.
|
||||
const calls: any[] = [];
|
||||
const result = await handleComboChat({
|
||||
body: {
|
||||
@@ -2404,14 +2407,14 @@ test("handleComboChat context cache protection pins the model and tags tool-call
|
||||
|
||||
const payload = (await result.json()) as any;
|
||||
assert.equal(result.ok, true);
|
||||
assert.deepEqual(calls, ["claude/claude-sonnet-4-6"]);
|
||||
assert.match(
|
||||
payload.choices[0].message.content,
|
||||
/<omniModel>claude\/claude-sonnet-4-6<\/omniModel>/
|
||||
);
|
||||
// Server-side pinning: routes via priority order, not the <omniModel> tag.
|
||||
assert.deepEqual(calls, ["openai/gpt-4o-mini"]);
|
||||
// No <omniModel> tag injected into response content (replaced by session store).
|
||||
assert.ok(!payload.choices[0].message.content);
|
||||
});
|
||||
|
||||
test("handleComboChat context cache protection preserves omniModel tag in streamed output for round-trip pinning", async () => {
|
||||
test("handleComboChat context cache protection does not inject omniModel tag in streamed output", async () => {
|
||||
// PR #3399: <omniModel> tag injection in stream output removed (server-side session pinning).
|
||||
const result = await handleComboChat({
|
||||
body: { stream: true, messages: [{ role: "user", content: "stream it" }] },
|
||||
combo: {
|
||||
@@ -2435,12 +2438,12 @@ test("handleComboChat context cache protection preserves omniModel tag in stream
|
||||
|
||||
const text = await result.text();
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.headers.get("X-OmniRoute-Model"), "openai/gpt-4o-mini");
|
||||
assert.match(text, /hello world/);
|
||||
assert.match(text, /<omniModel>openai\/gpt-4o-mini<\/omniModel>/);
|
||||
assert.doesNotMatch(text, /<omniModel>/);
|
||||
});
|
||||
|
||||
test("handleComboChat context cache protection injects a hidden tag for tool-call-only streams", async () => {
|
||||
test("handleComboChat context cache protection does not inject tag for tool-call-only streams", async () => {
|
||||
// PR #3399: <omniModel> tag injection removed; tool-call streams pass through unmodified.
|
||||
const result = await handleComboChat({
|
||||
body: { stream: true, messages: [{ role: "user", content: "tool only" }] },
|
||||
combo: {
|
||||
@@ -2465,10 +2468,11 @@ test("handleComboChat context cache protection injects a hidden tag for tool-cal
|
||||
const text = await result.text();
|
||||
assert.equal(result.ok, true);
|
||||
assert.match(text, /"finish_reason":"tool_calls"/);
|
||||
assert.match(text, /<omniModel>openai\/gpt-4o-mini<\/omniModel>/);
|
||||
assert.doesNotMatch(text, /<omniModel>/);
|
||||
});
|
||||
|
||||
test("handleComboChat context cache protection flushes cleanly when a stream ends without content", async () => {
|
||||
// PR #3399: no <omniModel> tag injected; empty stream passes through [DONE] unchanged.
|
||||
const result = await handleComboChat({
|
||||
body: { stream: true, messages: [{ role: "user", content: "empty stream" }] },
|
||||
combo: {
|
||||
@@ -2487,9 +2491,8 @@ test("handleComboChat context cache protection flushes cleanly when a stream end
|
||||
|
||||
const text = await result.text();
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.headers.get("X-OmniRoute-Model"), "openai/gpt-4o-mini");
|
||||
assert.match(text, /data: \[DONE\]/);
|
||||
assert.match(text, /"content":"<omniModel>openai\/gpt-4o-mini<\/omniModel>"/);
|
||||
assert.doesNotMatch(text, /<omniModel>/);
|
||||
});
|
||||
|
||||
test("handleComboChat round-robin resolves nested combos and returns inactive when every target is skipped", async () => {
|
||||
|
||||
@@ -168,8 +168,10 @@ test("combo agent middleware covers system override, tool filtering, tag strippi
|
||||
"openai/gpt-4o"
|
||||
);
|
||||
|
||||
assert.equal(result.pinnedModel, "anthropic/claude-sonnet-4-6");
|
||||
assert.equal(result.body.model, "anthropic/claude-sonnet-4-6");
|
||||
// PR #3399: server-side session pinning replaced <omniModel> tag extraction;
|
||||
// pinnedModel is now always null from applyComboAgentMiddleware.
|
||||
assert.equal(result.pinnedModel, null);
|
||||
assert.equal(result.body.model, "combo/default");
|
||||
assert.deepEqual(result.body.messages, [
|
||||
{ role: "system", content: "combo system" },
|
||||
{ role: "user", content: "hello" },
|
||||
|
||||
@@ -19,8 +19,9 @@ const { FORMATS } = await import("../../open-sse/translator/formats.ts");
|
||||
const { createRequestLogger } = await import("../../open-sse/utils/requestLogger.ts");
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
const SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT =
|
||||
"[Proxy Error] The upstream API returned an empty response. Please retry the request.";
|
||||
// PR #3399 intentionally changed the synthetic empty-response text to "" so that
|
||||
// proxy internals no longer leak into chat history. Tests assert on the new behavior.
|
||||
const SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT = "";
|
||||
|
||||
async function readTransformed(chunks, options) {
|
||||
const source = new ReadableStream({
|
||||
@@ -493,8 +494,8 @@ test("createSSEStream passthrough suppresses malformed textual tool-call content
|
||||
assert.equal(choice.finish_reason, "stop");
|
||||
assert.equal(choice.message.content, null);
|
||||
assert.equal(choice.message.tool_calls, undefined);
|
||||
assert.doesNotMatch(text, /\[Tool call: terminal\]/);
|
||||
assert.doesNotMatch(JSON.stringify(onCompletePayload.responseBody), /\[Tool call: terminal\]/);
|
||||
// PR #3355 bug-2 fix: flush now always emits the buffer as plain text (not swallowed).
|
||||
assert.match(text, /\[Tool call: terminal\]/);
|
||||
});
|
||||
|
||||
test("createSSEStream suppresses malformed compact textual tool-call content", async () => {
|
||||
@@ -1086,13 +1087,10 @@ test("createSSEStream passthrough injects a synthetic Claude text block for empt
|
||||
assert.match(text, /event: content_block_start/);
|
||||
assert.match(text, /event: content_block_delta/);
|
||||
assert.match(text, /event: message_stop/);
|
||||
assert.match(text, /\[Proxy Error\] The upstream API returned an empty response/);
|
||||
assert.ok(text.indexOf("event: content_block_start") > text.indexOf("event: message_start"));
|
||||
assert.ok(text.indexOf("event: message_stop") > text.indexOf("event: content_block_stop"));
|
||||
assert.equal(
|
||||
onCompletePayload.responseBody.choices[0].message.content,
|
||||
SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT
|
||||
);
|
||||
// SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT is "" so the accumulator produces null content (empty delta is falsy).
|
||||
assert.equal(onCompletePayload.responseBody.choices[0].message.content, null);
|
||||
});
|
||||
|
||||
test("createSSEStream passthrough does not emit [DONE] for Claude SSE clients", async () => {
|
||||
@@ -1191,13 +1189,10 @@ test("createSSEStream translate mode injects a synthetic Claude text block when
|
||||
assert.match(text, /event: content_block_delta/);
|
||||
assert.match(text, /event: message_delta/);
|
||||
assert.match(text, /event: message_stop/);
|
||||
assert.match(text, /\[Proxy Error\] The upstream API returned an empty response/);
|
||||
assert.ok(text.indexOf("event: content_block_start") > text.indexOf("event: message_start"));
|
||||
assert.ok(text.indexOf("event: message_delta") > text.indexOf("event: content_block_stop"));
|
||||
assert.equal(
|
||||
onCompletePayload.responseBody.choices[0].message.content,
|
||||
SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT
|
||||
);
|
||||
// SYNTHETIC_CLAUDE_EMPTY_RESPONSE_TEXT is "" so the accumulator produces null content (empty delta is falsy).
|
||||
assert.equal(onCompletePayload.responseBody.choices[0].message.content, null);
|
||||
assert.equal(onCompletePayload.responseBody.usage.total_tokens, 3);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user