import { describe, expect, it, vi } from "vitest"; import { runAssistantMcpDiscoveryRuntimeBridge } from "../src/services/assistantMcpDiscoveryRuntimeBridge"; function buildDeps(rows: Array>, error: string | null = null) { return { executeAddressMcpQuery: vi.fn(async () => ({ fetched_rows: rows.length, matched_rows: error ? 0 : rows.length, raw_rows: rows, rows: error ? [] : rows, error })) }; } describe("assistant MCP discovery runtime bridge", () => { it("composes planner, pilot executor, and answer draft without wiring the hot runtime", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }]) }); expect(result.schema_version).toBe("assistant_mcp_discovery_runtime_bridge_v1"); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.hot_runtime_wired).toBe(false); expect(result.pilot.mcp_execution_performed).toBe(true); expect(result.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(result.business_fact_answer_allowed).toBe(true); expect(result.user_facing_response_allowed).toBe(true); expect(result.reason_codes).toContain("runtime_bridge_not_wired_to_hot_assistant_answer"); }); it("keeps missing scope as clarification and does not authorize a business fact answer", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.requires_user_clarification).toBe(true); expect(result.pilot.mcp_execution_performed).toBe(false); expect(result.business_fact_answer_allowed).toBe(false); expect(result.answer_draft.next_step_line).toContain("Уточните контрагента"); }); it("keeps document-ready plans bounded when the pilot finds no confirmed rows", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "document", asked_action_family: "documents", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("checked_sources_only"); expect(result.hot_runtime_wired).toBe(false); expect(result.pilot.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1"); expect(result.pilot.mcp_execution_performed).toBe(true); expect(result.business_fact_answer_allowed).toBe(false); expect(result.reason_codes).toContain("runtime_bridge_status_checked_sources_only"); }); it("preserves the answer adapter boundary against internal mechanics leakage", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }]) }); const userFacing = [ result.answer_draft.headline, ...result.answer_draft.confirmed_lines, ...result.answer_draft.inference_lines, ...result.answer_draft.unknown_lines, ...result.answer_draft.limitation_lines, result.answer_draft.next_step_line ?? "" ].join("\n"); expect(userFacing).not.toContain("query_documents"); expect(userFacing).not.toContain("runtime_bridge"); expect(userFacing).not.toContain("primitive"); }); });