import { describe, expect, it, vi } from "vitest"; import { buildAssistantAddressOrchestrationRuntime } from "../src/services/assistantAddressOrchestrationRuntimeAdapter"; function buildInput(overrides: Record = {}) { const runAddressLlmPreDecompose = vi.fn(async () => ({ attempted: true, applied: true, effectiveMessage: "канон", reason: "normalized_fragment_applied" })); const resolveAddressFollowupCarryoverContext = vi.fn(() => ({ followupContext: { id: "ctx" } })); const resolveAssistantOrchestrationDecision = vi.fn(() => ({ runAddressLane: true, livingMode: "deep_analysis", livingReason: "address_mode_classifier_detected", toolGateDecision: "run_address_lane", toolGateReason: "address_mode_classifier_detected", orchestrationContract: { schema_version: "assistant_orchestration_contract_v1" } })); const buildAddressDialogContinuationContractV2 = vi.fn(() => ({ schema_version: "address_dialog_continuation_contract_v2" })); return { userMessage: "сырой вопрос", sessionItems: [], llmProvider: "openai", useMock: false, featureAddressLlmPredecomposeV1: true, runAddressLlmPreDecompose, buildAddressLlmPredecomposeContractV1: () => ({ schema_version: "address_llm_predecompose_contract_v1" }), sanitizeAddressMessageForFallback: () => "sanitized", toNonEmptyString: (value: unknown) => { if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; }, resolveAddressFollowupCarryoverContext, resolveAssistantOrchestrationDecision, buildAddressDialogContinuationContractV2, __spies: { runAddressLlmPreDecompose, resolveAddressFollowupCarryoverContext, resolveAssistantOrchestrationDecision, buildAddressDialogContinuationContractV2 }, ...overrides } as any; } describe("assistant address orchestration runtime adapter", () => { it("uses llm predecompose payload when feature is enabled", async () => { const input = buildInput(); const output = await buildAssistantAddressOrchestrationRuntime(input); expect(output.addressPreDecompose.reason).toBe("normalized_fragment_applied"); expect(output.addressInputMessage).toBe("канон"); expect(output.orchestrationDecision.runAddressLane).toBe(true); expect(output.livingModeDecision.mode).toBe("deep_analysis"); expect(output.addressRuntimeMeta.toolGateDecision).toBe("run_address_lane"); expect(output.addressRuntimeMeta.dialogContinuationContract).toEqual({ schema_version: "address_dialog_continuation_contract_v2" }); expect(input.__spies.runAddressLlmPreDecompose).toHaveBeenCalledTimes(1); expect(input.__spies.resolveAddressFollowupCarryoverContext).toHaveBeenCalledTimes(1); expect(input.__spies.resolveAssistantOrchestrationDecision).toHaveBeenCalledTimes(1); }); it("builds deterministic fallback predecompose payload when feature is disabled", async () => { const input = buildInput({ featureAddressLlmPredecomposeV1: false, llmProvider: "local", runAddressLlmPreDecompose: vi.fn(async () => { throw new Error("must not be called"); }), resolveAssistantOrchestrationDecision: vi.fn(() => ({ runAddressLane: false, livingMode: "chat", livingReason: "predecompose_unsupported_mode", toolGateDecision: "skip_address_lane", toolGateReason: "llm_predecompose_unsupported_mode", orchestrationContract: { schema_version: "assistant_orchestration_contract_v1" } })) }); const output = await buildAssistantAddressOrchestrationRuntime(input); expect(output.addressPreDecompose.attempted).toBe(false); expect(output.addressPreDecompose.applied).toBe(false); expect(output.addressPreDecompose.provider).toBe("local"); expect(output.addressPreDecompose.reason).toBe("disabled_by_feature_flag"); expect(output.addressPreDecompose.sanitizedUserMessage).toBe("sanitized"); expect(output.addressInputMessage).toBe("сырой вопрос"); expect(output.livingModeDecision.mode).toBe("chat"); expect(output.addressRuntimeMeta.toolGateDecision).toBe("skip_address_lane"); }); });