Files
NODEDC_1C/llm_normalizer/backend/tests/assistantMcpRuntimeBridge.test.ts
T

128 lines
4.4 KiB
TypeScript

import fs from "fs";
import os from "os";
import path from "path";
import { afterEach, describe, expect, it, vi } from "vitest";
const MCP_FLAG = "FEATURE_ASSISTANT_MCP_RUNTIME_V1";
const MCP_PROXY = "ASSISTANT_MCP_PROXY_URL";
const MCP_CHANNEL = "ASSISTANT_MCP_CHANNEL";
const ORIGINAL_ENV = {
[MCP_FLAG]: process.env[MCP_FLAG],
[MCP_PROXY]: process.env[MCP_PROXY],
[MCP_CHANNEL]: process.env[MCP_CHANNEL]
};
const TEMP_DIRS: string[] = [];
function restoreEnv(): void {
for (const key of [MCP_FLAG, MCP_PROXY, MCP_CHANNEL] as const) {
const original = ORIGINAL_ENV[key];
if (original === undefined) {
delete process.env[key];
} else {
process.env[key] = original;
}
}
}
function cleanupTempDirs(): void {
for (const dir of TEMP_DIRS.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
function createSnapshotRoot(): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "assistant-mcp-bridge-"));
TEMP_DIRS.push(root);
return root;
}
describe.sequential("assistant MCP runtime bridge", () => {
afterEach(() => {
vi.unstubAllGlobals();
restoreEnv();
cleanupTempDirs();
vi.resetModules();
});
it("does not call MCP when runtime flag is disabled", async () => {
process.env[MCP_FLAG] = "0";
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
const dataLayer = new AssistantDataLayer(createSnapshotRoot());
const result = await dataLayer.executeRouteRuntime("hybrid_store_plus_live", "Почему по счету 60.01 долг остался?");
expect(fetchMock).not.toHaveBeenCalled();
expect(result.summary.live_mcp).toBeUndefined();
});
it("uses MCP live probe for hybrid route when runtime flag is enabled", async () => {
process.env[MCP_FLAG] = "1";
process.env[MCP_PROXY] = "http://127.0.0.1:6003";
process.env[MCP_CHANNEL] = "default";
const payload = JSON.stringify({
success: true,
data: [
{
Период: "2026-03-01T00:00:00",
Регистратор: "Списание с расчетного счета 0001",
СчетДт: "60.01",
СчетКт: "51",
Сумма: 15000
},
{
Период: "2026-03-02T00:00:00",
Регистратор: "Операция бухгалтерская 0002",
СчетДт: "91.02",
СчетКт: "51",
Сумма: 900
}
]
});
const fetchMock = vi.fn(async () => new Response(payload, { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
const dataLayer = new AssistantDataLayer(createSnapshotRoot());
const result = await dataLayer.executeRouteRuntime("hybrid_store_plus_live", "Проверь 60.01 и 60.02: оплата есть, долг остался");
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(result.status).toBe("ok");
expect(result.items.length).toBeGreaterThan(0);
const summary = result.summary as Record<string, unknown>;
const liveSummary = summary.live_mcp as Record<string, unknown>;
expect(liveSummary.status).toBe("ok");
expect(liveSummary.channel).toBe("default");
const firstItem = result.items[0] as Record<string, unknown>;
expect(firstItem.source_layer).toBe("mcp_live_probe");
});
it("keeps snapshot fallback when MCP responds with error", async () => {
process.env[MCP_FLAG] = "1";
process.env[MCP_PROXY] = "http://127.0.0.1:6003";
process.env[MCP_CHANNEL] = "default";
const payload = JSON.stringify({
success: false,
data: null,
error: "channel_not_connected"
});
const fetchMock = vi.fn(async () => new Response(payload, { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const { AssistantDataLayer } = await import("../src/services/assistantDataLayer");
const dataLayer = new AssistantDataLayer(createSnapshotRoot());
const result = await dataLayer.executeRouteRuntime("hybrid_store_plus_live", "Проверь 60.01 остаток");
expect(fetchMock).toHaveBeenCalledTimes(1);
const summary = result.summary as Record<string, unknown>;
const liveSummary = summary.live_mcp as Record<string, unknown>;
expect(liveSummary.status).toBe("error");
expect(result.limitations.some((item) => item.includes("Live MCP"))).toBe(true);
});
});