import { describe, expect, it, vi } from "vitest"; import { runAssistantLivingChatAttemptRuntime, type RunAssistantLivingChatAttemptRuntimeInput } from "../src/services/assistantLivingChatAttemptRuntimeAdapter"; type TestResponse = { ok: boolean; llm: string }; function buildInput( overrides: Partial> = {} ): RunAssistantLivingChatAttemptRuntimeInput { return { sessionId: "asst-1", userMessage: "контрагенты со сверкой", sessionItems: [{ role: "user", text: "контекст" }], modeDecision: { mode: "chat", reason: "living_chat_signal_detected" }, sessionScope: { knownOrganizations: [], selectedOrganization: null, activeOrganization: null }, addressRuntimeMeta: null, traceIdFactory: () => "chat-trace-1", toNonEmptyString: (value: unknown) => (typeof value === "string" && value.trim() ? value.trim() : null), mergeKnownOrganizations: (values: string[]) => values, hasAssistantDataScopeMetaQuestionSignal: () => false, shouldHandleAsAssistantCapabilityMetaQuery: () => false, hasDestructiveDataActionSignal: () => false, hasDangerOrCoercionSignal: () => false, hasOperationalAdminActionRequestSignal: () => false, hasOrganizationFactLookupSignal: () => false, hasOrganizationFactFollowupSignal: () => false, shouldEmitOrganizationSelectionReply: () => false, hasAssistantCapabilityQuestionSignal: () => false, resolveDataScopeProbe: async () => null, applyScriptGuard: (text: string) => ({ text, applied: false, reason: null }), applyGroundingGuard: (payload: { chatText: string }) => ({ text: payload.chatText, applied: false, reason: null }), buildAssistantSafetyRefusalReply: () => "safety", buildAssistantDataScopeContractReply: () => "scope", buildAssistantOrganizationFactBoundaryReply: () => "boundary", buildAssistantDataScopeSelectionReply: () => "selection", buildAssistantOperationalBoundaryReply: () => "operational", buildAssistantCapabilityContractReply: () => "capability", appendItem: () => {}, getSession: () => ({ session_id: "asst-1", updated_at: "", items: [], investigation_state: null }), persistSession: () => {}, cloneConversation: (items: unknown[]) => items, logEvent: () => {}, messageIdFactory: () => "msg-1", nowIso: () => "2026-04-10T00:00:00.000Z", payload: { llmProvider: "openai", apiKey: "key", model: "gpt-5", baseUrl: "http://localhost", temperature: 0.2, maxOutputTokens: 300 }, chatClient: { chat: async () => ({ outputText: "chat-output" }) }, loadAssistantCanonExcerpt: () => "canon", sanitizeOutgoingAssistantText: (value: unknown, fallback = "fallback") => { const text = String(value ?? "").trim(); return text || fallback; }, defaultModel: "gpt-5", defaultBaseUrl: "http://localhost", defaultApiKey: "key", ...overrides }; } describe("assistant living chat attempt runtime adapter", () => { it("wires llm runtime output into living handler execute callback", async () => { const runLivingChatLlm = vi.fn(async () => "llm-answer"); const runLivingChatHandler = vi.fn(async (input) => { const llm = await input.executeLlmChat(); return { ok: true, llm }; }); const response = await runAssistantLivingChatAttemptRuntime( buildInput({ runLivingChatHandler, runLivingChatLlm }) ); expect(runLivingChatHandler).toHaveBeenCalledTimes(1); expect(runLivingChatLlm).toHaveBeenCalledWith( expect.objectContaining({ userMessage: "контрагенты со сверкой" }) ); expect(response).toEqual({ ok: true, llm: "llm-answer" }); }); it("returns null when delegated handler returns null", async () => { const runLivingChatLlm = vi.fn(async () => "llm-answer"); const runLivingChatHandler = vi.fn(async () => null); const response = await runAssistantLivingChatAttemptRuntime( buildInput({ runLivingChatHandler, runLivingChatLlm }) ); expect(response).toBeNull(); expect(runLivingChatLlm).not.toHaveBeenCalled(); }); });