import { describe, expect, it } from "vitest"; import { buildAssistantMcpDiscoveryResponseCandidate } from "../src/services/assistantMcpDiscoveryResponseCandidate"; function entryPoint(overrides: Record = {}) { return { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint", entry_status: "bridge_executed", hot_runtime_wired: false, discovery_attempted: true, turn_input: { adapter_status: "ready" }, bridge: { bridge_status: "answer_draft_ready", user_facing_response_allowed: true, business_fact_answer_allowed: true, requires_user_clarification: false, answer_draft: { answer_mode: "confirmed_with_bounded_inference", headline: "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод.", confirmed_lines: ["1C activity rows were found for counterparty SVK"], inference_lines: ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"], unknown_lines: ["Legal registration date is not proven by this MCP discovery pilot"], limitation_lines: ["query_documents was skipped internally", "MCP fetch window was limited"], next_step_line: null } }, reason_codes: ["runtime_entry_point_bridge_executed"], ...overrides } as any; } describe("assistant MCP discovery response candidate", () => { it("builds a Russian guarded candidate from a confirmed discovery draft", () => { const candidate = buildAssistantMcpDiscoveryResponseCandidate(entryPoint()); expect(candidate.candidate_status).toBe("ready_for_guarded_use"); expect(candidate.hot_runtime_wired).toBe(false); expect(candidate.reply_type).toBe("partial_coverage"); expect(candidate.eligible_for_future_hot_runtime).toBe(true); expect(candidate.reply_text).toContain("Коротко:"); expect(candidate.reply_text).toContain("В 1С найдены строки активности по контрагенту SVK."); expect(candidate.reply_text).toContain("Юридическая дата регистрации этим поиском не подтверждена."); expect(candidate.reply_text).not.toContain("query_documents"); expect(candidate.reply_text).not.toContain("primitive"); }); it("returns not applicable when discovery was skipped for an exact supported route", () => { const candidate = buildAssistantMcpDiscoveryResponseCandidate({ schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint", entry_status: "skipped_not_applicable", hot_runtime_wired: false, discovery_attempted: false, turn_input: { adapter_status: "not_applicable" }, bridge: null, reason_codes: [] } as any); expect(candidate.candidate_status).toBe("not_applicable"); expect(candidate.reply_text).toBeNull(); expect(candidate.eligible_for_future_hot_runtime).toBe(false); }); it("creates a clarification candidate from a clarification bridge", () => { const candidate = buildAssistantMcpDiscoveryResponseCandidate( entryPoint({ bridge: { bridge_status: "needs_clarification", user_facing_response_allowed: true, business_fact_answer_allowed: false, requires_user_clarification: true, answer_draft: { answer_mode: "needs_clarification", headline: "Нужно уточнить контекст перед поиском в 1С.", confirmed_lines: [], inference_lines: [], unknown_lines: [], limitation_lines: [], next_step_line: "Уточните контрагента, период или организацию." } } }) ); expect(candidate.candidate_status).toBe("clarification_candidate"); expect(candidate.reply_type).toBe("clarification_required"); expect(candidate.reply_text).toContain("Нужно уточнить контекст"); expect(candidate.eligible_for_future_hot_runtime).toBe(true); }); it("does not expose unsupported bridge output as a future hot candidate", () => { const candidate = buildAssistantMcpDiscoveryResponseCandidate( entryPoint({ bridge: { bridge_status: "unsupported", user_facing_response_allowed: true, business_fact_answer_allowed: false, requires_user_clarification: false, answer_draft: null } }) ); expect(candidate.candidate_status).toBe("unsupported"); expect(candidate.reply_type).toBe("no_grounded_answer"); expect(candidate.reply_text).toBeNull(); expect(candidate.eligible_for_future_hot_runtime).toBe(false); }); });