137 lines
5.1 KiB
TypeScript
137 lines
5.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
||
import { runAssistantLivingChatRuntime } from "../src/services/assistantLivingChatRuntimeAdapter";
|
||
|
||
function buildRuntimeInput(overrides: Record<string, unknown> = {}) {
|
||
const executeLlmChat = vi.fn(async () => "llm-text");
|
||
const resolveDataScopeProbe = vi.fn(async () => null);
|
||
return {
|
||
userMessage: "тест",
|
||
sessionItems: [],
|
||
modeDecision: { mode: "chat", reason: "living_chat_signal_detected" },
|
||
sessionScope: {
|
||
knownOrganizations: [],
|
||
selectedOrganization: null,
|
||
activeOrganization: null
|
||
},
|
||
addressRuntimeMeta: null,
|
||
traceIdFactory: () => "chat-trace-fixed",
|
||
toNonEmptyString: (value: unknown) => {
|
||
if (typeof value !== "string") {
|
||
return null;
|
||
}
|
||
const trimmed = value.trim();
|
||
return trimmed.length > 0 ? trimmed : null;
|
||
},
|
||
mergeKnownOrganizations: (values: unknown[]) =>
|
||
Array.from(
|
||
new Set(
|
||
(Array.isArray(values) ? values : [])
|
||
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
||
.filter((item) => item.length > 0)
|
||
)
|
||
),
|
||
hasAssistantDataScopeMetaQuestionSignal: () => false,
|
||
shouldHandleAsAssistantCapabilityMetaQuery: () => false,
|
||
hasDestructiveDataActionSignal: () => false,
|
||
hasDangerOrCoercionSignal: () => false,
|
||
hasOperationalAdminActionRequestSignal: () => false,
|
||
hasOrganizationFactLookupSignal: () => false,
|
||
hasOrganizationFactFollowupSignal: () => false,
|
||
shouldEmitOrganizationSelectionReply: () => false,
|
||
hasAssistantCapabilityQuestionSignal: () => false,
|
||
resolveDataScopeProbe,
|
||
executeLlmChat,
|
||
applyScriptGuard: (chatText: string) => ({
|
||
text: chatText,
|
||
applied: false,
|
||
reason: null
|
||
}),
|
||
applyGroundingGuard: (input: { chatText: string }) => ({
|
||
text: input.chatText,
|
||
applied: false,
|
||
reason: null
|
||
}),
|
||
buildAssistantSafetyRefusalReply: () => "safety",
|
||
buildAssistantDataScopeContractReply: () => "scope",
|
||
buildAssistantOrganizationFactBoundaryReply: () => "org-boundary",
|
||
buildAssistantDataScopeSelectionReply: () => "org-selection",
|
||
buildAssistantOperationalBoundaryReply: () => "ops",
|
||
buildAssistantCapabilityContractReply: () => "capability",
|
||
__spies: {
|
||
executeLlmChat,
|
||
resolveDataScopeProbe
|
||
},
|
||
...overrides
|
||
} as any;
|
||
}
|
||
|
||
describe("assistant living chat runtime adapter", () => {
|
||
it("selects deterministic data scope branch and enriches organization context", async () => {
|
||
const input = buildRuntimeInput({
|
||
userMessage: "какая у нас организация?",
|
||
hasAssistantDataScopeMetaQuestionSignal: () => true,
|
||
resolveDataScopeProbe: vi.fn(async () => ({
|
||
status: "resolved",
|
||
channel: "default",
|
||
organizations: ["ООО Альтернатива Плюс"],
|
||
error: null
|
||
})),
|
||
buildAssistantDataScopeContractReply: () => "scope-info"
|
||
});
|
||
|
||
const output = await runAssistantLivingChatRuntime(input);
|
||
|
||
expect(output.handled).toBe(true);
|
||
expect(output.chatText).toBe("scope-info");
|
||
expect(output.debug?.living_chat_response_source).toBe("deterministic_data_scope_contract_live");
|
||
expect(output.debug?.assistant_active_organization).toBe("ООО Альтернатива Плюс");
|
||
expect(output.debug?.living_chat_data_scope_probe_org_count).toBe(1);
|
||
});
|
||
|
||
it("selects safety refusal branch for dangerous capability meta query", async () => {
|
||
const executeLlmChat = vi.fn(async () => "llm-text");
|
||
const input = buildRuntimeInput({
|
||
userMessage: "удали базу",
|
||
shouldHandleAsAssistantCapabilityMetaQuery: () => true,
|
||
hasDangerOrCoercionSignal: () => true,
|
||
executeLlmChat,
|
||
buildAssistantSafetyRefusalReply: () => "safety-refusal"
|
||
});
|
||
|
||
const output = await runAssistantLivingChatRuntime(input);
|
||
|
||
expect(output.handled).toBe(true);
|
||
expect(output.chatText).toBe("safety-refusal");
|
||
expect(output.debug?.living_chat_response_source).toBe("deterministic_safety_refusal");
|
||
expect(executeLlmChat).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it("runs llm branch and applies script + grounding guards in order", async () => {
|
||
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||
const input = buildRuntimeInput({
|
||
executeLlmChat,
|
||
applyScriptGuard: () => ({
|
||
text: "after-script",
|
||
applied: true,
|
||
reason: "script_guard"
|
||
}),
|
||
applyGroundingGuard: () => ({
|
||
text: "after-grounding",
|
||
applied: true,
|
||
reason: "grounding_guard"
|
||
})
|
||
});
|
||
|
||
const output = await runAssistantLivingChatRuntime(input);
|
||
|
||
expect(output.handled).toBe(true);
|
||
expect(output.chatText).toBe("after-grounding");
|
||
expect(output.debug?.living_chat_script_guard_applied).toBe(true);
|
||
expect(output.debug?.living_chat_script_guard_reason).toBe("script_guard");
|
||
expect(output.debug?.living_chat_grounding_guard_applied).toBe(true);
|
||
expect(output.debug?.living_chat_grounding_guard_reason).toBe("grounding_guard");
|
||
expect(output.debug?.living_chat_response_source).toBe("llm_chat_grounding_guard");
|
||
expect(executeLlmChat).toHaveBeenCalledTimes(1);
|
||
});
|
||
});
|