88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { createAssistantBoundaryPolicy } from "../src/services/assistantBoundaryPolicy";
|
||
|
||
function createPolicy() {
|
||
return createAssistantBoundaryPolicy({
|
||
activeMcpChannel: "default",
|
||
normalizeOrganizationScopeValue: (value: unknown) => {
|
||
if (value === null || value === undefined) {
|
||
return null;
|
||
}
|
||
const text = String(value ?? "")
|
||
.replace(/\\/g, "")
|
||
.replace(/([А-Яа-яA-Za-z])"([А-Яа-яA-Za-z])/gu, "$1в$2")
|
||
.trim();
|
||
return text.length > 0 ? text : null;
|
||
},
|
||
toNonEmptyString: (value: unknown) => {
|
||
if (value === null || value === undefined) {
|
||
return null;
|
||
}
|
||
const text = String(value ?? "").trim();
|
||
return text.length > 0 ? text : null;
|
||
},
|
||
hasOrganizationFactLookupSignal: (message: string) => /возраст|дата регистрации/i.test(message)
|
||
});
|
||
}
|
||
|
||
describe("assistantBoundaryPolicy", () => {
|
||
it("builds deterministic data-scope reply for single organization", () => {
|
||
const policy = createPolicy();
|
||
|
||
const reply = policy.buildAssistantDataScopeContractReply({
|
||
status: "resolved",
|
||
channel: "finance",
|
||
organizations: ["ООО Альтернатива Плюс"]
|
||
});
|
||
|
||
expect(reply).toContain("Сейчас доступна организация");
|
||
expect(reply).toContain("ООО Альтернатива Плюс");
|
||
expect(reply).not.toContain("MCP");
|
||
expect(reply.toLowerCase()).not.toContain("read-only");
|
||
});
|
||
|
||
it("normalizes noisy organization labels in data-scope reply", () => {
|
||
const policy = createPolicy();
|
||
|
||
const reply = policy.buildAssistantDataScopeContractReply({
|
||
status: "resolved",
|
||
channel: "default",
|
||
organizations: ['ООО \\Альтернати"а Плюс\\', 'ООО \\Лайс"уд\\']
|
||
});
|
||
|
||
expect(reply).toContain('ООО Альтернатива Плюс');
|
||
expect(reply).toContain('ООО Лайсвуд');
|
||
expect(reply).not.toContain('\\"');
|
||
expect(reply).not.toContain("\\");
|
||
});
|
||
|
||
it("strips unexpected CJK fragments from live chat reply", () => {
|
||
const policy = createPolicy();
|
||
|
||
const guarded = policy.applyLivingChatScriptGuard(
|
||
"Прошу прощения, но я не могу продолжать этот разговор. 随时关注。",
|
||
"че как"
|
||
);
|
||
|
||
expect(guarded.applied).toBe(true);
|
||
expect(guarded.reason).toBe("unexpected_cjk_fragment_stripped");
|
||
expect(guarded.text).toContain("Прошу прощения");
|
||
expect(/[\u3400-\u9FFF\uF900-\uFAFF]/u.test(guarded.text)).toBe(false);
|
||
});
|
||
|
||
it("blocks ungrounded organization fact answer with deterministic boundary reply", () => {
|
||
const policy = createPolicy();
|
||
|
||
const guarded = policy.applyLivingChatGroundingGuard({
|
||
userMessage: "какой возраст у альтернативы?",
|
||
chatText: "Для ООО Альтернатива Плюс дата регистрации 01.07.2015, возраст 8 лет.",
|
||
organization: "ООО Альтернатива Плюс"
|
||
});
|
||
|
||
expect(guarded.applied).toBe(true);
|
||
expect(guarded.reason).toBe("organization_fact_without_live_source_blocked");
|
||
expect(guarded.text.toLowerCase()).toContain("не буду называть");
|
||
expect(guarded.text).not.toContain("01.07.2015");
|
||
});
|
||
});
|