import { describe, expect, it, vi } from "vitest"; import { buildAssistantMcpDiscoveryAnswerDraft } from "../src/services/assistantMcpDiscoveryAnswerAdapter"; import { executeAssistantMcpDiscoveryPilot } from "../src/services/assistantMcpDiscoveryPilotExecutor"; import { planAssistantMcpDiscovery } from "../src/services/assistantMcpDiscoveryPlanner"; 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 answer adapter", () => { it("turns confirmed lifecycle evidence into a human-safe bounded answer draft", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST", Контрагент: "SVK" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.internal_mechanics_allowed).toBe(false); expect(draft.headline).toContain("подтвержденная активность"); expect(draft.confirmed_lines[0]).toContain("SVK"); expect(draft.inference_lines[0]).toContain("меньше месяца"); expect(draft.inference_lines.join("\n")).toContain("Первая найденная активность: 2020-01-15"); expect(draft.inference_lines.join("\n")).toContain("не юридически подтвержденный возраст регистрации"); expect(draft.unknown_lines).toContain("Legal registration date is not proven by this MCP discovery pilot"); expect(draft.must_not_claim).toContain("Do not present inferred activity duration as a formally confirmed legal fact."); expect(draft.reason_codes).toContain("answer_contains_unknown_fact_boundary"); }); it("uses checked-sources mode when MCP failed and avoids confirmed facts", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([], "MCP fetch failed: timeout")); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("checked_sources_only"); expect(draft.confirmed_lines).toEqual([]); expect(draft.limitation_lines).toContain("MCP fetch failed: timeout"); expect(draft.next_step_line).toContain("MCP"); expect(draft.must_not_claim).toContain("Do not claim a confirmed business fact when confirmed_facts is empty."); }); it("asks for clarification when discovery did not execute due to missing scope", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot(planner, buildDeps([])); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.answer_mode).toBe("needs_clarification"); expect(draft.headline).toBe("Нужно уточнить контекст перед поиском в 1С."); expect(draft.next_step_line).toContain("Уточните контрагента"); expect(draft.must_not_claim).toContain("Do not claim rows were checked when mcp_execution_performed=false."); }); it("turns value-flow evidence into a bounded turnover answer draft", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020" } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([ { Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" }, { Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const confirmedText = draft.confirmed_lines.join("\n"); expect(draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(draft.headline).toContain("денежных движений"); expect(confirmedText).toContain("3 750,50 руб."); expect(confirmedText).toContain("2020-01-15"); expect(confirmedText).toContain("2020-02-20"); expect(draft.unknown_lines).toContain("Full turnover outside the checked period is not proven by this MCP discovery pilot"); expect(draft.must_not_claim).toContain("Do not claim full all-time turnover unless the checked period and coverage prove it."); expect(draft.limitation_lines.join("\n")).not.toContain("pilot_"); }); it("does not leak primitive names or query text into user-facing lines", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST", Контрагент: "SVK" }]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const userFacing = [ draft.headline, ...draft.confirmed_lines, ...draft.inference_lines, ...draft.unknown_lines, ...draft.limitation_lines, draft.next_step_line ?? "" ].join("\n"); expect(userFacing).not.toContain("query_documents"); expect(userFacing).not.toContain("SELECT"); expect(userFacing).not.toContain("ВЫБРАТЬ"); expect(userFacing).not.toContain("primitive"); }); it("verbalizes activity duration from first and latest confirmed 1C rows", async () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] } }); const pilot = await executeAssistantMcpDiscoveryPilot( planner, buildDeps([ { Period: "2020-01-15T00:00:00", Counterparty: "SVK" }, { Period: "2023-12-20T00:00:00", Counterparty: "SVK" } ]) ); const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); const inferenceText = draft.inference_lines.join("\n"); expect(inferenceText).toContain("3 года 11 месяцев"); expect(inferenceText).toContain("2020-01-15"); expect(inferenceText).toContain("2023-12-20"); expect(inferenceText).toContain("не юридически подтвержденный возраст регистрации"); expect(draft.reason_codes).toContain("pilot_derived_activity_period_from_confirmed_rows"); }); });