diff --git a/tests/unit/combo-routing-engine.test.ts b/tests/unit/combo-routing-engine.test.ts index dbec1fdff7..5e828f17cc 100644 --- a/tests/unit/combo-routing-engine.test.ts +++ b/tests/unit/combo-routing-engine.test.ts @@ -17,8 +17,10 @@ const { } = await import("../../open-sse/services/combo.ts"); const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts"); const { registerStrategy } = await import("../../open-sse/services/autoCombo/routerStrategy.ts"); +const { touchSession, clearSessions } = await import("../../open-sse/services/sessionManager.ts"); const core = await import("../../src/lib/db/core.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); const evalsDb = await import("../../src/lib/db/evals.ts"); const { saveModelsDevCapabilities, clearModelsDevCapabilities } = await import("../../src/lib/modelsDevSync.ts"); @@ -145,6 +147,7 @@ test.beforeEach(async () => { resetAllCircuitBreakers(); resetAllSemaphores(); _resetAllDecks(); + clearSessions(); await resetStorage(); }); @@ -2131,6 +2134,86 @@ test("handleComboChat auto strategy honors LKGP after filtering to tool-capable assert.equal(calls[0], "claude/claude-sonnet-4-6"); }); +test("handleComboChat auto strategy preserves selected same-provider connection identity", async () => { + const connA = await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "OpenAI A", + apiKey: "sk-auto-conn-a", + defaultModel: "gpt-4o-mini", + }); + const connB = await providersDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "OpenAI B", + apiKey: "sk-auto-conn-b", + defaultModel: "gpt-4o-mini", + }); + touchSession("sticky-auto-session", connB.id); + + const calls: Array<{ modelStr: string; connectionId: string | null | undefined }> = []; + const result = await handleComboChat({ + body: { messages: [{ role: "user", content: "Continue the existing conversation" }] }, + combo: { + id: "auto-same-provider-connection", + name: "auto-same-provider-connection", + strategy: "auto", + models: [ + { + kind: "model", + providerId: "openai", + model: "openai/gpt-4o-mini", + connectionId: connA.id, + label: "OpenAI A", + }, + { + kind: "model", + providerId: "openai", + model: "openai/gpt-4o-mini", + connectionId: connB.id, + label: "OpenAI B", + }, + ], + autoConfig: { + candidatePool: ["openai"], + explorationRate: 0, + weights: { + quota: 0, + health: 0, + costInv: 0, + latencyInv: 0, + taskFit: 0, + stability: 0, + tierPriority: 0, + tierAffinity: 0, + specificityMatch: 0, + contextAffinity: 1, + resetWindowAffinity: 0, + connectionDensity: 0, + }, + }, + }, + handleSingleModel: async (_body: any, modelStr: any, target: any) => { + calls.push({ modelStr, connectionId: target?.connectionId }); + return okResponse(); + }, + isModelAvailable: async () => true, + log: createLog(), + settings: null, + relayOptions: { sessionId: "sticky-auto-session" } as any, + allCombos: null, + }); + + assert.equal(result.ok, true); + assert.equal(calls.length, 1, "successful auto dispatch should call exactly one selected target"); + assert.deepEqual(calls, [ + { + modelStr: "openai/gpt-4o-mini", + connectionId: connB.id, + }, + ]); +}); + test("handleComboChat standalone lkgp strategy prioritizes the last known good provider", async () => { await settingsDb.setLKGP("standalone-lkgp", "standalone-lkgp", "anthropic"); diff --git a/tests/unit/virtual-auto-combo.test.ts b/tests/unit/virtual-auto-combo.test.ts index 313f2e1db9..2113cd7f2c 100644 --- a/tests/unit/virtual-auto-combo.test.ts +++ b/tests/unit/virtual-auto-combo.test.ts @@ -129,6 +129,43 @@ test("createVirtualAutoCombo excludes web-session providers with irrelevant prov assert.equal(combo.autoConfig.candidatePool.includes("chatgpt-web"), false); }); +test("createVirtualAutoCombo preserves multiple same-provider web-session candidates", async () => { + const connA = await providersDb.createProviderConnection({ + provider: "qwen-web", + authType: "apikey", + name: "Qwen Web Session A", + providerSpecificData: { token: "qwen-web-session-token-a" }, + defaultModel: "qwen3-coder-plus", + }); + const connB = await providersDb.createProviderConnection({ + provider: "qwen-web", + authType: "apikey", + name: "Qwen Web Session B", + providerSpecificData: { token: "qwen-web-session-token-b" }, + defaultModel: "qwen3-coder-plus", + }); + + const combo: VirtualComboResult = await virtualFactory.createVirtualAutoCombo("coding"); + + const qwenWebModels = combo.models.filter((model) => model.providerId === "qwen-web"); + assert.equal( + qwenWebModels.length, + 2, + "same-provider web sessions must not collapse to one target" + ); + assert.deepEqual( + new Set(qwenWebModels.map((model) => model.connectionId)), + new Set([connA.id, connB.id]), + "same-provider web sessions should map back to their exact provider_connection rows" + ); + assert.ok(qwenWebModels.every((model) => model.model === "qwen-web/qwen3-coder-plus")); + assert.equal( + combo.autoConfig.candidatePool.filter((provider) => provider === "qwen-web").length, + 1, + "provider pool remains provider-scoped while model entries preserve connection identity" + ); +}); + test("createVirtualAutoCombo includes cookie web-session providers with required cookie data", async () => { await providersDb.createProviderConnection({ provider: "chatgpt-web", @@ -141,7 +178,10 @@ test("createVirtualAutoCombo includes cookie web-session providers with required const combo: VirtualComboResult = await virtualFactory.createVirtualAutoCombo("coding"); const chatgptWeb = combo.models.find((model) => model.providerId === "chatgpt-web"); - assert.ok(chatgptWeb, "cookie web-session providers with required cookie data should be candidates"); + assert.ok( + chatgptWeb, + "cookie web-session providers with required cookie data should be candidates" + ); assert.equal(chatgptWeb.model, "chatgpt-web/gpt-4o"); assert.ok(combo.autoConfig.candidatePool.includes("chatgpt-web")); });