From d09b7445e54d7c75c7b027f22e5b55578a5a8ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 22 Mar 2026 03:41:41 -0400 Subject: [PATCH] test(02-01): add failing tests for connector-matching heuristic - 24 test cases covering matchConnector, inferTaskType, getConnectorStrengths - Tests for preference mapping, fallback behavior, edge cases - TDD RED phase: module does not exist yet --- tests/unit/connector-matching.test.ts | 262 ++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 tests/unit/connector-matching.test.ts diff --git a/tests/unit/connector-matching.test.ts b/tests/unit/connector-matching.test.ts new file mode 100644 index 00000000..f183d51d --- /dev/null +++ b/tests/unit/connector-matching.test.ts @@ -0,0 +1,262 @@ +import { describe, expect, it } from "vitest"; +import { + matchConnector, + inferTaskType, + getConnectorStrengths, + TASK_TYPE_PREFERENCE, + CONNECTOR_STRENGTHS, + ROLE_DEPARTMENT_HINTS, +} from "../../src/main/connector-matching"; +import type { ConnectorId, ConnectorStatus } from "@shared/types"; + +function makeConnector(id: ConnectorId, status: ConnectorStatus = "ready", label?: string) { + return { id, status, label: label ?? id }; +} + +describe("connector-matching", () => { + describe("TASK_TYPE_PREFERENCE", () => { + it("maps code tasks to claude_local first", () => { + expect(TASK_TYPE_PREFERENCE.code[0]).toBe("claude_local"); + }); + + it("maps research tasks to gemini_local first", () => { + expect(TASK_TYPE_PREFERENCE.research[0]).toBe("gemini_local"); + }); + + it("maps content tasks to claude_local first", () => { + expect(TASK_TYPE_PREFERENCE.content[0]).toBe("claude_local"); + }); + + it("maps management tasks to claude_local first", () => { + expect(TASK_TYPE_PREFERENCE.management[0]).toBe("claude_local"); + }); + + it("maps general tasks to claude_local first", () => { + expect(TASK_TYPE_PREFERENCE.general[0]).toBe("claude_local"); + }); + + it("includes all three connectors for every task type", () => { + for (const key of Object.keys(TASK_TYPE_PREFERENCE)) { + const prefs = TASK_TYPE_PREFERENCE[key as keyof typeof TASK_TYPE_PREFERENCE]; + expect(prefs).toHaveLength(3); + expect(prefs).toContain("claude_local"); + expect(prefs).toContain("codex_local"); + expect(prefs).toContain("gemini_local"); + } + }); + }); + + describe("inferTaskType", () => { + it("returns code for engineering department", () => { + expect(inferTaskType("engineering")).toBe("code"); + }); + + it("returns research for research department", () => { + expect(inferTaskType("research")).toBe("research"); + }); + + it("returns content for marketing department", () => { + expect(inferTaskType("marketing")).toBe("content"); + }); + + it("returns content for product department", () => { + expect(inferTaskType("product")).toBe("content"); + }); + + it("returns management for hr department", () => { + expect(inferTaskType("hr")).toBe("management"); + }); + + it("returns management for finance department", () => { + expect(inferTaskType("finance")).toBe("management"); + }); + + it("returns management for executive department", () => { + expect(inferTaskType("executive")).toBe("management"); + }); + + it("returns content for design department", () => { + expect(inferTaskType("design")).toBe("content"); + }); + + it("returns content for customer_support department", () => { + expect(inferTaskType("customer_support")).toBe("content"); + }); + + it("returns management for operations department", () => { + expect(inferTaskType("operations")).toBe("management"); + }); + + it("returns content for sales department", () => { + expect(inferTaskType("sales")).toBe("content"); + }); + + it("returns management for legal department", () => { + expect(inferTaskType("legal")).toBe("management"); + }); + + it("returns general for null department", () => { + expect(inferTaskType(null)).toBe("general"); + }); + + it("returns general for undefined department", () => { + expect(inferTaskType(undefined)).toBe("general"); + }); + + it("returns general for unknown department", () => { + expect(inferTaskType("unknown_dept")).toBe("general"); + }); + }); + + describe("getConnectorStrengths", () => { + it("returns non-empty string for claude_local", () => { + const result = getConnectorStrengths("claude_local"); + expect(result).toBeTruthy(); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(10); + }); + + it("returns non-empty string for codex_local", () => { + const result = getConnectorStrengths("codex_local"); + expect(result).toBeTruthy(); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(10); + }); + + it("returns non-empty string for gemini_local", () => { + const result = getConnectorStrengths("gemini_local"); + expect(result).toBeTruthy(); + expect(typeof result).toBe("string"); + expect(result.length).toBeGreaterThan(10); + }); + }); + + describe("matchConnector", () => { + const allReady = [ + makeConnector("claude_local", "ready", "Claude Code"), + makeConnector("codex_local", "ready", "Codex"), + makeConnector("gemini_local", "ready", "Gemini CLI"), + ]; + + it("recommends claude_local for code tasks when all ready", () => { + const result = matchConnector({ + taskType: "code", + agentRole: "engineer", + agentDepartment: "engineering", + availableConnectors: allReady, + }); + expect(result.recommendedConnectorId).toBe("claude_local"); + }); + + it("recommends gemini_local for research tasks when all ready", () => { + const result = matchConnector({ + taskType: "research", + agentRole: "researcher", + agentDepartment: "research", + availableConnectors: allReady, + }); + expect(result.recommendedConnectorId).toBe("gemini_local"); + }); + + it("recommends claude_local for content tasks when all ready", () => { + const result = matchConnector({ + taskType: "content", + agentRole: "marketer", + agentDepartment: "marketing", + availableConnectors: [ + makeConnector("claude_local", "ready", "Claude Code"), + ], + }); + expect(result.recommendedConnectorId).toBe("claude_local"); + }); + + it("falls back when preferred connector is offline", () => { + const result = matchConnector({ + taskType: "research", + agentRole: "researcher", + agentDepartment: "research", + availableConnectors: [ + makeConnector("gemini_local", "not_installed", "Gemini CLI"), + makeConnector("claude_local", "ready", "Claude Code"), + makeConnector("codex_local", "ready", "Codex"), + ], + }); + // gemini is not ready, so should fallback to claude_local (next in research preference) + expect(result.recommendedConnectorId).toBe("claude_local"); + }); + + it("uses first available connector when none are ready", () => { + const result = matchConnector({ + taskType: "code", + agentRole: "engineer", + agentDepartment: "engineering", + availableConnectors: [ + makeConnector("claude_local", "detected", "Claude Code"), + makeConnector("codex_local", "auth_required", "Codex"), + ], + }); + // None ready, should pick first available + expect(result.recommendedConnectorId).toBe("claude_local"); + }); + + it("returns claude_local as ultimate fallback with empty connector list", () => { + const result = matchConnector({ + taskType: "code", + agentRole: "engineer", + agentDepartment: "engineering", + availableConnectors: [], + }); + expect(result.recommendedConnectorId).toBe("claude_local"); + }); + + it("returns a different fallback when multiple connectors are ready", () => { + const result = matchConnector({ + taskType: "code", + agentRole: "engineer", + agentDepartment: "engineering", + availableConnectors: allReady, + }); + expect(result.fallbackConnectorId).not.toBeNull(); + expect(result.fallbackConnectorId).not.toBe(result.recommendedConnectorId); + }); + + it("returns null fallback when only one connector is ready", () => { + const result = matchConnector({ + taskType: "code", + agentRole: "engineer", + agentDepartment: "engineering", + availableConnectors: [ + makeConnector("claude_local", "ready", "Claude Code"), + makeConnector("codex_local", "not_installed", "Codex"), + ], + }); + expect(result.fallbackConnectorId).toBeNull(); + }); + + it("includes a descriptive reason string", () => { + const result = matchConnector({ + taskType: "code", + agentRole: "engineer", + agentDepartment: "engineering", + availableConnectors: allReady, + }); + expect(result.reason).toBeTruthy(); + expect(typeof result.reason).toBe("string"); + expect(result.reason.length).toBeGreaterThan(5); + }); + + it("handles degraded connectors as non-ready", () => { + const result = matchConnector({ + taskType: "research", + agentRole: "analyst", + agentDepartment: "research", + availableConnectors: [ + makeConnector("gemini_local", "degraded", "Gemini CLI"), + makeConnector("claude_local", "ready", "Claude Code"), + ], + }); + // gemini is degraded, should skip to claude_local + expect(result.recommendedConnectorId).toBe("claude_local"); + }); + }); +});