import { describe, expect, it, vi } from "vitest"; import { tryHandleAssistantLivingChatRuntime } from "../src/services/assistantLivingChatHandlerRuntimeAdapter"; function buildInput(overrides: Record = {}) { return { sessionId: "asst-1", userMessage: "question", sessionItems: [], 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: unknown[]) => values.map((v) => String(v)), hasAssistantDataScopeMetaQuestionSignal: () => false, shouldHandleAsAssistantCapabilityMetaQuery: () => false, hasDestructiveDataActionSignal: () => false, hasDangerOrCoercionSignal: () => false, hasOperationalAdminActionRequestSignal: () => false, hasOrganizationFactLookupSignal: () => false, hasOrganizationFactFollowupSignal: () => false, shouldEmitOrganizationSelectionReply: () => false, hasAssistantCapabilityQuestionSignal: () => false, resolveDataScopeProbe: async () => null, executeLlmChat: async () => "chat answer", 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 } as any), persistSession: () => {}, cloneConversation: (items: any[]) => items, logEvent: vi.fn(), messageIdFactory: () => "msg-1", nowIso: () => "2026-04-10T00:00:00.000Z", ...overrides } as any; } describe("assistant living chat handler runtime adapter", () => { it("returns finalized response when runtime is handled", async () => { const runLivingChatRuntime = vi.fn(async () => ({ handled: true, chatText: "chat", debug: { trace_id: "chat-1" } })); const finalizeLivingChatTurn = vi.fn(() => ({ response: { ok: true, lane: "chat" } })); const response = await tryHandleAssistantLivingChatRuntime( buildInput({ runLivingChatRuntime, finalizeLivingChatTurn }) ); expect(runLivingChatRuntime).toHaveBeenCalledTimes(1); expect(finalizeLivingChatTurn).toHaveBeenCalledWith( expect.objectContaining({ assistantReply: "chat", replyType: "factual_with_explanation" }) ); expect(response).toEqual({ ok: true, lane: "chat" }); }); it("returns null when runtime is not handled", async () => { const finalizeLivingChatTurn = vi.fn(); const response = await tryHandleAssistantLivingChatRuntime( buildInput({ runLivingChatRuntime: async () => ({ handled: false, chatText: "", debug: null }), finalizeLivingChatTurn }) ); expect(response).toBeNull(); expect(finalizeLivingChatTurn).not.toHaveBeenCalled(); }); it("logs warn and returns null on runtime error", async () => { const logEvent = vi.fn(); const response = await tryHandleAssistantLivingChatRuntime( buildInput({ runLivingChatRuntime: async () => { throw new Error("boom"); }, logEvent }) ); expect(response).toBeNull(); expect(logEvent).toHaveBeenCalledWith( expect.objectContaining({ level: "warn", message: "assistant_living_chat_failed_fallback_to_deep" }) ); }); });