168 lines
7.4 KiB
TypeScript
168 lines
7.4 KiB
TypeScript
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<Record<string, unknown>>, 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");
|
||
});
|
||
});
|