import { describe, expect, it, vi } from "vitest"; import { runAssistantAddressLaneRuntime, type AssistantAddressFollowupCarryoverLike, type AssistantAddressLaneLike } from "../src/services/assistantAddressLaneRuntimeAdapter"; function limitedLane(category: string): AssistantAddressLaneLike { return { handled: true, debug: { limited_reason_category: category } }; } function factualLane(): AssistantAddressLaneLike { return { handled: true, debug: {} }; } function unhandledLane(): AssistantAddressLaneLike { return { handled: false, debug: {} }; } describe("assistant address lane runtime adapter", () => { it("returns contextual lane immediately when preferred contextual attempt is factual", async () => { const carryover: AssistantAddressFollowupCarryoverLike = { followupContext: { scope: "ctx" } }; const runAddressLaneAttempt = vi.fn(async () => factualLane()); const result = await runAssistantAddressLaneRuntime({ userMessage: "сырой вопрос", addressInputMessage: "нормализованный вопрос", carryover, shouldPreferContextualLane: true, canRetryWithRawUserMessage: true, runAddressLaneAttempt, isRetryableAddressLimitedResult: (lane) => Boolean(lane?.debug?.limited_reason_category) }); expect(result.handled).toBe(true); expect(result.selection?.messageUsed).toBe("нормализованный вопрос"); expect(result.selection?.carryMeta).toBe(carryover); expect(result.retryAudit.attempted).toBe(false); expect(runAddressLaneAttempt).toHaveBeenCalledTimes(1); expect(runAddressLaneAttempt).toHaveBeenCalledWith("нормализованный вопрос", carryover); }); it("retries with raw message after limited result and returns factual retry", async () => { const carryover: AssistantAddressFollowupCarryoverLike = { followupContext: { scope: "ctx" } }; const runAddressLaneAttempt = vi .fn() .mockResolvedValueOnce(limitedLane("empty_match")) // primary .mockResolvedValueOnce(limitedLane("empty_match")) // contextual .mockResolvedValueOnce(factualLane()); // raw contextual retry const result = await runAssistantAddressLaneRuntime({ userMessage: "сырой вопрос", addressInputMessage: "нормализованный вопрос", carryover, shouldPreferContextualLane: false, canRetryWithRawUserMessage: true, runAddressLaneAttempt, isRetryableAddressLimitedResult: (lane) => Boolean(lane?.debug?.limited_reason_category) }); expect(result.handled).toBe(true); expect(result.selection?.messageUsed).toBe("сырой вопрос"); expect(result.selection?.carryMeta).toBe(carryover); expect(result.retryAudit.attempted).toBe(true); expect(result.retryAudit.reason).toBe("limited_result_retry_with_raw_message"); expect(result.retryAudit.initial_limited_category).toBe("empty_match"); expect(result.retryAudit.retry_used_followup_context).toBe(true); expect(result.retryAudit.retry_result_category).toBe(null); expect(runAddressLaneAttempt).toHaveBeenCalledTimes(3); }); it("returns pending limited result when retry is disabled", async () => { const runAddressLaneAttempt = vi .fn() .mockResolvedValueOnce(limitedLane("missing_anchor")) // primary .mockResolvedValueOnce(unhandledLane()); // contextual fallback const result = await runAssistantAddressLaneRuntime({ userMessage: "сырой вопрос", addressInputMessage: "нормализованный вопрос", carryover: { followupContext: { scope: "ctx" } }, shouldPreferContextualLane: false, canRetryWithRawUserMessage: false, runAddressLaneAttempt, isRetryableAddressLimitedResult: (lane) => Boolean(lane?.debug?.limited_reason_category) }); expect(result.handled).toBe(true); expect(result.selection?.messageUsed).toBe("нормализованный вопрос"); expect(result.selection?.addressLane.debug?.limited_reason_category).toBe("missing_anchor"); expect(result.retryAudit.attempted).toBe(false); }); });