diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index 0d73fd9dc4..9125fcea08 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -13,7 +13,7 @@ import Modal from "@/shared/components/Modal"; import Toggle from "@/shared/components/Toggle"; import Tooltip from "@/shared/components/Tooltip"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; -import { filterActiveConnections } from "@/shared/utils/connectionStatus"; +import { filterUsableConnections } from "@/shared/utils/connectionStatus"; import { FieldLabelWithHelp, WeightTotalBar } from "./parts"; import { useComboProxyAssignments } from "./useComboProxyAssignments"; import { ResponseValidationEditor, type ResponseValidationValue } from "./ResponseValidationEditor"; @@ -771,10 +771,7 @@ export default function CombosPage() { if (combosRes.ok) setCombos((combosData.combos || []).filter((c) => !c.isHidden)); if (providersRes.ok) { - const active = filterActiveConnections(providersData.connections || []).filter( - (c) => c.testStatus === "active" || c.testStatus === "success" - ); - setActiveProviders(active); + setActiveProviders(filterUsableConnections(providersData.connections || [])); } if (metricsRes.ok) setMetrics(metricsData.metrics || {}); setProviderNodes(nodesData.nodes || []); diff --git a/src/shared/utils/connectionStatus.ts b/src/shared/utils/connectionStatus.ts index 0d871ae130..a2a4ff1446 100644 --- a/src/shared/utils/connectionStatus.ts +++ b/src/shared/utils/connectionStatus.ts @@ -24,3 +24,17 @@ export function filterActiveConnections( if (!Array.isArray(connections)) return []; return connections.filter((connection) => connection?.isActive !== false); } + +/** + * Filters connections down to the ones a builder UI can actually route to: + * enabled (`isActive !== false`) AND last tested healthy ("active"/"success"). + * Both gates must be applied together — filtering on `testStatus` alone keeps + * disabled connections that carry a stale healthy status. + */ +export function filterUsableConnections( + connections: T[] | null | undefined +): T[] { + return filterActiveConnections(connections).filter( + (connection) => connection.testStatus === "active" || connection.testStatus === "success" + ); +} diff --git a/tests/unit/connection-status-filter-active-2526.test.ts b/tests/unit/connection-status-filter-active-2526.test.ts index 24d3d05b9d..ff0e5eb2e3 100644 --- a/tests/unit/connection-status-filter-active-2526.test.ts +++ b/tests/unit/connection-status-filter-active-2526.test.ts @@ -1,7 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; -import { filterActiveConnections } from "@/shared/utils/connectionStatus"; +import { filterActiveConnections, filterUsableConnections } from "@/shared/utils/connectionStatus"; // Ported from decolua/9router#2526 — the combos builder listed provider // connections the user had explicitly disabled, because the page only @@ -25,23 +25,26 @@ test("filterActiveConnections returns an empty list for invalid input", () => { assert.deepEqual(filterActiveConnections(null), []); }); -test("combos page fetchData filter mirrors filterActiveConnections + testStatus gate", () => { +test("filterUsableConnections applies the isActive gate before the testStatus gate", () => { // Regression for the exact bug: a disabled connection with a stale - // "active" testStatus must NOT survive the combined filter used in - // src/app/(dashboard)/dashboard/combos/page.tsx fetchData(). + // "active" testStatus must NOT survive the combined filter that + // src/app/(dashboard)/dashboard/combos/page.tsx fetchData() calls. const connections = [ { id: "healthy", isActive: true, testStatus: "active" }, + { id: "healthy-success", isActive: true, testStatus: "success" }, { id: "disabled-but-stale-status", isActive: false, testStatus: "active" }, { id: "disabled-success-status", isActive: false, testStatus: "success" }, { id: "enabled-not-tested", isActive: true, testStatus: "untested" }, + { id: "legacy-no-isActive", testStatus: "active" }, ]; - const result = filterActiveConnections(connections).filter( - (c) => c.testStatus === "active" || c.testStatus === "success" - ); - assert.deepEqual( - result.map((c) => c.id), - ["healthy"] + filterUsableConnections(connections).map((c) => c.id), + ["healthy", "healthy-success", "legacy-no-isActive"] ); }); + +test("filterUsableConnections returns an empty list for invalid input", () => { + assert.deepEqual(filterUsableConnections(undefined), []); + assert.deepEqual(filterUsableConnections(null), []); +});