NODEDC_1C/llm_normalizer/backend/tests/assistantLivingChatHandlerR...

119 lines
4.1 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { tryHandleAssistantLivingChatRuntime } from "../src/services/assistantLivingChatHandlerRuntimeAdapter";
function buildInput(overrides: Record<string, unknown> = {}) {
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,
hasLivingChatSignal: () => true,
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"
})
);
});
});