import { afterEach, describe, expect, it, vi } from "vitest"; const { executeAddressMcpQueryMock } = vi.hoisted(() => ({ executeAddressMcpQueryMock: vi.fn() })); vi.mock("../src/services/addressMcpClient", async () => { const actual = await vi.importActual( "../src/services/addressMcpClient" ); return { ...actual, executeAddressMcpQuery: executeAddressMcpQueryMock }; }); import { AddressQueryService } from "../src/services/addressQueryService"; import { runAddressDecomposeStage } from "../src/services/address_runtime/decomposeStage"; afterEach(() => { executeAddressMcpQueryMock.mockReset(); vi.restoreAllMocks(); }); describe("inventory profitability selected-object regressions", () => { const followupContext = { previous_intent: "inventory_on_hand_as_of_date" as const, previous_filters: { organization: "ООО \\Альтернатива Плюс\\", as_of_date: "2020-05-31", period_from: "2020-05-01", period_to: "2020-05-31" }, previous_anchor_type: "unknown" as const, previous_anchor_value: null }; const selectedObjectProfitabilityMessage = 'По выбранному объекту "Четки Пост (84*117)": а сколько денег мы заработали с продажжи этих четок'; it("routes selected-object profitability wording into an item profitability intent", () => { const result = runAddressDecomposeStage(selectedObjectProfitabilityMessage, followupContext); expect(result).not.toBeNull(); expect(result?.intent.intent).toBe("inventory_profitability_for_item"); expect(result?.intent.intent).not.toBe("customer_revenue_and_payments"); expect(result?.filters.extracted_filters.item).toBe("Четки Пост (84*117)"); expect(result?.filters.extracted_filters.period_from).toBe("2020-05-01"); expect(result?.filters.extracted_filters.period_to).toBe("2020-05-31"); }); it("answers selected-object profitability with a bounded document spread instead of a recipe gap", async () => { executeAddressMcpQueryMock.mockResolvedValueOnce({ fetched_rows: 2, matched_rows: 2, raw_rows: [ { Period: "2020-05-10T00:00:00Z", Registrator: "Поступление товаров и услуг 000000001 от 10.05.2020", AccountDt: "41.01", AccountKt: "60.01", Amount: 500, Quantity: 10, SubcontoDt1: "Четки Пост (84*117)", SubcontoDt3: "Основной склад", Counterparty: "ООО \\Поставщик\\", Organization: "ООО \\Альтернатива Плюс\\" }, { Period: "2020-05-20T00:00:00Z", Registrator: "Реализация товаров и услуг 000000017 от 20.05.2020", AccountDt: "62.01", AccountKt: "41.01", Amount: 900, Quantity: 10, SubcontoKt1: "Четки Пост (84*117)", SubcontoKt3: "Основной склад", Counterparty: "ИП Покупатель", Organization: "ООО \\Альтернатива Плюс\\" } ], rows: [], error: null }); const service = new AddressQueryService(); const result = await service.tryHandle(selectedObjectProfitabilityMessage, { followupContext }); expect(result?.handled).toBe(true); expect(result?.response_type).toBe("FACTUAL_SUMMARY"); expect(result?.debug.detected_intent).toBe("inventory_profitability_for_item"); expect(result?.debug.selected_recipe).toBe("address_inventory_profitability_for_item_v1"); expect(result?.debug.capability_id).toBe("inventory_inventory_profitability_for_item"); expect(result?.debug.mcp_call_status).toBe("matched_non_empty"); expect(result?.debug.extracted_filters?.item).toBe("Четки Пост (84*117)"); const reply = String(result?.reply_text ?? ""); expect(reply).toContain("Четки Пост (84*117)"); expect(reply).toContain("900"); expect(reply).toContain("500"); expect(reply).toContain("400"); expect(reply).toContain("Маржинальность"); expect(reply).toContain("не чистая прибыль компании"); expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1); }); });