/**
* @jest-environment node
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { EmployeeApiClient } from "../../src/api/employee-api";
import eventBus from "../../src/common/event-bus";
import { SessionManager } from "../../src/tools/auth-tools";
import { EmployeeHandler, registerEmployeeTools } from "../../src/tools/employee-tools";
import { DataProcessor } from "../../src/utils/data-processor";
import { XlsxParser } from "../../src/utils/xlsx-parser";
// Мокаем все зависимости
jest.mock("../../src/api/employee-api");
jest.mock("../../src/common/event-bus");
jest.mock("../../src/utils/data-processor");
jest.mock("../../src/utils/performance-monitor", () => ({
measure: jest.fn().mockImplementation((name, fn) => fn()),
}));
jest.mock("../../src/utils/xlsx-parser");
jest.mock("../../src/tools/auth-tools");
const mockedEmployeeApiClient = jest.mocked(EmployeeApiClient);
const mockedEventBus = jest.mocked(eventBus);
const mockedDataProcessor = jest.mocked(DataProcessor);
const mockedXlsxParser = jest.mocked(XlsxParser);
const mockedSessionManager = jest.mocked(SessionManager);
// Мокаем console.log и console.error
const mockConsoleLog = jest.spyOn(console, "log").mockImplementation();
const mockConsoleError = jest.spyOn(console, "error").mockImplementation();
describe("Employee Tools", () => {
let mockServer: jest.Mocked<McpServer>;
let mockSessionManagerInstance: jest.Mocked<SessionManager>;
let mockApiClientInstance: jest.Mocked<EmployeeApiClient>;
let handler: EmployeeHandler;
beforeEach(() => {
jest.clearAllMocks();
mockConsoleLog.mockClear();
mockConsoleError.mockClear();
// Создаем мок сервера
mockServer = {
tool: jest.fn(),
} as any;
// Создаем мок SessionManager
mockSessionManagerInstance = {
getApiKey: jest.fn(),
getCurrentApiKey: jest.fn(),
} as any;
// Создаем мок EmployeeApiClient
mockApiClientInstance = {
checkEmployeeUniqueness: jest.fn(),
handleEmployeeConflict: jest.fn(),
createEmployee: jest.fn(),
createEmployeeForce: jest.fn(),
updateEmployee: jest.fn(),
} as any;
mockedSessionManager.getInstance = jest.fn().mockReturnValue(mockSessionManagerInstance);
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
handler = new EmployeeHandler();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("registerEmployeeTools", () => {
it("should register all employee tools", () => {
registerEmployeeTools(mockServer);
expect(mockServer.tool).toHaveBeenCalledTimes(6);
expect(mockServer.tool).toHaveBeenCalledWith(
"employee_add_one",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"employee_add_list",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"employee_import_xlsx",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"employee_telegram_handler",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"employee_resolve_conflict",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
expect(mockServer.tool).toHaveBeenCalledWith(
"employee_force_create",
expect.any(Object),
expect.any(Object),
expect.any(Function)
);
});
});
describe("EmployeeHandler", () => {
describe("processAddOne", () => {
it("should handle empty rawText", async () => {
const result = await handler.processAddOne("", "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Пустой текст для обработки"],
report: "Ошибка: Не предоставлен текст для добавления сотрудника",
});
});
it("should handle empty companyId", async () => {
const result = await handler.processAddOne("Иванов Иван", "");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Не указан ID компании"],
report: "Ошибка: Не указан companyId",
});
});
it("should handle invalid employee data", async () => {
const mockDraft = {
isValid: false,
errors: ["Фамилия не указана", "Email не указан"],
};
mockedDataProcessor.parseAdvancedText.mockReturnValue(mockDraft as any);
mockedDataProcessor.validateEmployeeDraft.mockReturnValue(mockDraft as any);
mockedDataProcessor.validateDataQuality.mockReturnValue({ score: 30 });
const result = await handler.processAddOne("Invalid data", "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Фамилия не указана", "Email не указан"],
report: "Ошибка валидации (качество: 30%): Фамилия не указана, Email не указан",
});
});
it("should successfully create new employee", async () => {
const mockDraft = {
isValid: true,
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
};
const mockUniquenessCheck = {
found: false,
conflictType: null,
employees: [],
};
const mockConflictResolution = {
action: "create",
message: "Создать нового сотрудника",
};
const mockCreateResult = { id: "emp123", name: "Иванов Иван" };
mockedDataProcessor.parseAdvancedText.mockReturnValue(mockDraft as any);
mockedDataProcessor.validateEmployeeDraft.mockReturnValue(mockDraft as any);
mockedDataProcessor.validateDataQuality.mockReturnValue({ score: 95 });
mockApiClientInstance.checkEmployeeUniqueness.mockResolvedValue(mockUniquenessCheck);
mockApiClientInstance.handleEmployeeConflict.mockResolvedValue(mockConflictResolution);
mockApiClientInstance.createEmployee.mockResolvedValue(mockCreateResult);
const result = await handler.processAddOne("Иванов Иван ivan@example.com", "company123");
expect(result).toEqual({
success: true,
processed: 1,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
},
],
report: "✅ Сотрудник Иванов Иван успешно добавлен (качество данных: 95%)",
});
});
it("should handle API errors", async () => {
const mockDraft = {
isValid: true,
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
};
mockedDataProcessor.parseAdvancedText.mockReturnValue(mockDraft as any);
mockedDataProcessor.validateEmployeeDraft.mockReturnValue(mockDraft as any);
mockedDataProcessor.validateDataQuality.mockReturnValue({ score: 95 });
mockApiClientInstance.checkEmployeeUniqueness.mockRejectedValue(new Error("API Error"));
const result = await handler.processAddOne("Иванов Иван ivan@example.com", "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["API Error"],
report: "❌ Ошибка при добавлении сотрудника: API Error",
});
});
});
});
describe("processAddList", () => {
it("should handle empty rawText", async () => {
const result = await handler.processAddList("", "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Пустой текст для обработки"],
report: "Ошибка: Не предоставлен текст для добавления списка сотрудников",
});
});
it("should handle empty companyId", async () => {
const result = await handler.processAddList("Иванов Иван\nПетров Петр", "");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Не указан ID компании"],
report: "Ошибка: Не указан companyId",
});
});
it("should successfully process multiple employees", async () => {
const rawText = "Иванов Иван ivan@example.com\nПетров Петр petr@example.com";
// Мокаем DataProcessor для обработки списка
mockedDataProcessor.parseAdvancedText
.mockReturnValueOnce({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
})
.mockReturnValueOnce({
firstName: "Петр",
lastName: "Петров",
email: "petr@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft
.mockReturnValueOnce({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
})
.mockReturnValueOnce({
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
email: "petr@example.com",
phones: ["+79009876543"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality
.mockReturnValueOnce({ score: 95, issues: [] })
.mockReturnValueOnce({ score: 90, issues: [] });
// Мокаем checkEmployeeUniqueness для каждого сотрудника
const mockApiClientInstance = {
checkEmployeeUniqueness: jest
.fn()
.mockResolvedValueOnce({ found: false, message: "No conflict" })
.mockResolvedValueOnce({ found: false, message: "No conflict" }),
createEmployee: jest
.fn()
.mockResolvedValueOnce({ id: "emp1", name: "Иванов Иван" })
.mockResolvedValueOnce({ id: "emp2", name: "Петров Петр" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processAddList(rawText, "company123");
expect(result).toEqual({
success: true,
processed: 2,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
middleName: undefined,
email: "ivan@example.com",
phones: ["+79001234567"],
},
{
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
middleName: undefined,
email: "petr@example.com",
phones: ["+79009876543"],
},
],
errors: undefined,
report: undefined,
});
});
it("should handle mixed valid and invalid employees", async () => {
const rawText = "Иванов Иван ivan@example.com\nInvalid data\nПетров Петр petr@example.com";
// Мокаем DataProcessor
mockedDataProcessor.parseAdvancedText
.mockReturnValueOnce({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
})
.mockReturnValueOnce({
firstName: "",
lastName: "",
email: "",
isValid: false,
errors: ["Фамилия не указана", "Email не указан"],
})
.mockReturnValueOnce({
firstName: "Петр",
lastName: "Петров",
email: "petr@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft
.mockReturnValueOnce({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
})
.mockReturnValueOnce({
companyId: "company123",
lastName: "",
firstName: "",
email: "",
phones: [],
isValid: false,
errors: ["Фамилия не указана", "Email не указан"],
})
.mockReturnValueOnce({
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
email: "petr@example.com",
phones: ["+79009876543"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality
.mockReturnValueOnce({ score: 95, issues: [] })
.mockReturnValueOnce({ score: 90, issues: [] });
// Мокаем checkEmployeeUniqueness для каждого сотрудника
const mockApiClientInstance = {
checkEmployeeUniqueness: jest
.fn()
.mockResolvedValueOnce({ found: false, message: "No conflict" })
.mockResolvedValueOnce({ found: false, message: "No conflict" }),
createEmployee: jest
.fn()
.mockResolvedValueOnce({ id: "emp1", name: "Иванов Иван" })
.mockResolvedValueOnce({ id: "emp2", name: "Петров Петр" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processAddList(rawText, "company123");
expect(result).toEqual({
success: true,
processed: 2,
failed: 1,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
middleName: undefined,
email: "ivan@example.com",
phones: ["+79001234567"],
},
{
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
middleName: undefined,
email: "petr@example.com",
phones: ["+79009876543"],
},
],
errors: ['Строка "Invalid data": Фамилия не указана, Email не указан'],
report: undefined,
});
});
});
describe("processImportXlsx", () => {
it("should handle empty fileUrl", async () => {
// Мокаем XlsxParser для ошибки
mockedXlsxParser.parseFromUrl.mockResolvedValue({
success: false,
employees: [],
totalRows: 0,
validRows: 0,
invalidRows: 0,
duplicates: 0,
errors: ["Не указан URL файла"],
report: "Ошибка: Не указан fileUrl",
});
const result = await handler.processImportXlsx("", "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 0,
duplicates: 0,
errors: ["Не указан URL файла"],
report: "Ошибка: Не указан fileUrl",
});
});
it("should handle empty companyId", async () => {
// Мокаем XlsxParser для ошибки
mockedXlsxParser.parseFromUrl.mockResolvedValue({
success: false,
employees: [],
totalRows: 0,
validRows: 0,
invalidRows: 0,
duplicates: 0,
errors: ["Не указан ID компании"],
report: "Ошибка: Не указан companyId",
});
const result = await handler.processImportXlsx("https://example.com/file.xlsx", "");
expect(result).toEqual({
success: false,
processed: 0,
failed: 0,
duplicates: 0,
errors: ["Не указан ID компании"],
report: "Ошибка: Не указан companyId",
});
});
it("should successfully import from XLSX", async () => {
const fileUrl = "https://example.com/employees.xlsx";
// Мокаем XlsxParser
mockedXlsxParser.parseFromUrl.mockResolvedValue({
success: true,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
},
{
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
email: "petr@example.com",
phones: ["+79009876543"],
isValid: true,
},
],
totalRows: 2,
validRows: 2,
invalidRows: 0,
duplicates: 0,
errors: [],
report: "✅ Импортировано 2 сотрудников из Excel файла",
});
// Мокаем checkEmployeeUniqueness для каждого сотрудника
const mockApiClientInstance = {
checkEmployeeUniqueness: jest
.fn()
.mockResolvedValueOnce({ found: false, message: "No conflict" })
.mockResolvedValueOnce({ found: false, message: "No conflict" }),
createEmployee: jest
.fn()
.mockResolvedValueOnce({ id: "emp1", name: "Иванов Иван" })
.mockResolvedValueOnce({ id: "emp2", name: "Петров Петр" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processImportXlsx(fileUrl, "company123");
expect(result).toEqual({
success: true,
processed: 2,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
},
{
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
email: "petr@example.com",
phones: ["+79009876543"],
},
],
report: expect.stringContaining("✅ Импортировано 2 сотрудников из Excel файла"),
});
});
it("should handle XLSX parsing errors", async () => {
const fileUrl = "https://example.com/invalid.xlsx";
// Мокаем XlsxParser для ошибки
mockedXlsxParser.parseFromUrl.mockResolvedValue({
success: false,
employees: [],
totalRows: 0,
validRows: 0,
invalidRows: 0,
duplicates: 0,
errors: ["Ошибка парсинга файла"],
report: "❌ Ошибка при импорте из Excel файла",
});
const result = await handler.processImportXlsx(fileUrl, "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 0,
duplicates: 0,
errors: ["Ошибка парсинга файла"],
report: "❌ Ошибка при импорте из Excel файла",
});
});
});
describe("processTelegramMessage", () => {
it("should handle empty message", async () => {
const result = await handler.processTelegramMessage("", "company123", "user123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Пустое сообщение"],
report: "❌ Ошибка: Получено пустое сообщение",
});
});
it("should handle empty companyId", async () => {
const result = await handler.processTelegramMessage("Иванов Иван ivan@example.com", "", "user123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Не указан ID компании"],
report: "❌ Ошибка: Не указан companyId",
});
});
it("should handle empty userId", async () => {
const result = await handler.processTelegramMessage("Иванов Иван ivan@example.com", "company123", "");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Не указан ID пользователя"],
report: "❌ Ошибка: Не указан userId",
});
});
it("should successfully process Telegram message", async () => {
const message = "Иванов Иван ivan@example.com";
// Мокаем DataProcessor
mockedDataProcessor.parseAdvancedText.mockReturnValue({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft.mockReturnValue({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality.mockReturnValue({ score: 95, issues: [] });
// Мокаем checkEmployeeUniqueness и handleEmployeeConflict
const mockApiClientInstance = {
checkEmployeeUniqueness: jest.fn().mockResolvedValue({ found: false, message: "No conflict" }),
createEmployee: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
handleEmployeeConflict: jest.fn().mockResolvedValue({ action: "create", message: "No conflict" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processTelegramMessage(message, "company123", "user123");
expect(result).toEqual({
success: true,
processed: 1,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
middleName: undefined,
email: "ivan@example.com",
phones: ["+79001234567"],
},
],
report: "✅ Сотрудник Иванов Иван успешно добавлен (качество данных: 95%)",
});
});
});
describe("processConflictResolution", () => {
it("should handle unknown action", async () => {
const employeeData = { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" };
const result = await handler.processConflictResolution(employeeData, "exact", "unknown", undefined, "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["Неизвестное действие для разрешения конфликта"],
report: "❌ Ошибка: Неизвестное действие",
});
});
it("should successfully resolve conflict with create action", async () => {
const employeeData = { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" };
// Мокаем API клиент
const mockApiClientInstance = {
createEmployeeForce: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processConflictResolution(employeeData, "exact", "create", undefined, "company123");
expect(result).toEqual({
success: true,
processed: 1,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
middleName: undefined,
email: "ivan@example.com",
phones: undefined,
},
],
report: "✅ Сотрудник Иванов Иван успешно создан",
});
});
it("should successfully resolve conflict with update action", async () => {
const employeeData = { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" };
// Мокаем API клиент
const mockApiClientInstance = {
updateEmployee: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processConflictResolution(employeeData, "exact", "update", "emp123", "company123");
expect(result).toEqual({
success: true,
processed: 1,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
middleName: undefined,
email: "ivan@example.com",
phones: undefined,
},
],
report: "✅ Сотрудник Иванов Иван успешно обновлён",
});
});
});
describe("processForceCreate", () => {
it("should successfully force create employee", async () => {
const employeeData = { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" };
// Мокаем API клиент
const mockApiClientInstance = {
createEmployeeForce: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processForceCreate(employeeData, "company123");
expect(result).toEqual({
success: true,
processed: 1,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
},
],
report: "✅ Сотрудник Иванов Иван принудительно создан",
});
});
it("should successfully force create employee with reason", async () => {
const employeeData = { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" };
// Мокаем API клиент
const mockApiClientInstance = {
createEmployeeForce: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processForceCreate(employeeData, "company123", "session123", "Manual override");
expect(result).toEqual({
success: true,
processed: 1,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
},
],
report: "✅ Сотрудник Иванов Иван принудительно создан (причина: Manual override)",
});
});
it("should handle API errors", async () => {
const employeeData = { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" };
// Мокаем API клиент для ошибки
const mockApiClientInstance = {
createEmployeeForce: jest.fn().mockRejectedValue(new Error("API Error")),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processForceCreate(employeeData, "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["API Error"],
report: "❌ Ошибка при принудительном создании: API Error",
});
});
});
describe("processAddList error handling", () => {
it("should handle errors during processing", async () => {
const rawText = "Иванов Иван ivan@example.com";
// Мокаем DataProcessor для ошибки
mockedDataProcessor.parseAdvancedText.mockImplementation(() => {
throw new Error("DataProcessor error");
});
const result = await handler.processAddList(rawText, "company123");
expect(result).toEqual({
success: false,
processed: 0,
failed: 1,
duplicates: 0,
errors: ["DataProcessor error"],
report: "❌ Ошибка при обработке списка: DataProcessor error",
});
});
});
describe("processTelegramMessage command detection", () => {
it("should detect single employee command", async () => {
const message = "добавить сотрудника: Иванов Иван ivan@example.com";
// Мокаем DataProcessor
mockedDataProcessor.parseAdvancedText.mockReturnValue({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft.mockReturnValue({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality.mockReturnValue({ score: 90, issues: [] });
// Мокаем checkEmployeeUniqueness и handleEmployeeConflict
const mockApiClientInstance = {
checkEmployeeUniqueness: jest.fn().mockResolvedValue({ found: false, message: "No conflict" }),
createEmployee: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
handleEmployeeConflict: jest.fn().mockResolvedValue({ action: "create", message: "No conflict" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processTelegramMessage(message, "company123", "user123");
expect(result).toEqual({
success: true,
processed: 1,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
middleName: undefined,
email: "ivan@example.com",
phones: ["+79001234567"],
},
],
report: "✅ Сотрудник Иванов Иван успешно добавлен (качество данных: 90%)",
});
});
it("should auto-detect multiple lines as list", async () => {
const message = "Иванов Иван ivan@example.com\nПетров Петр petr@example.com";
// Мокаем DataProcessor для обработки списка
mockedDataProcessor.parseAdvancedText
.mockReturnValueOnce({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
})
.mockReturnValueOnce({
firstName: "Петр",
lastName: "Петров",
email: "petr@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft
.mockReturnValueOnce({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
})
.mockReturnValueOnce({
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
email: "petr@example.com",
phones: ["+79009876543"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality
.mockReturnValueOnce({ score: 95, issues: [] })
.mockReturnValueOnce({ score: 90, issues: [] });
// Мокаем checkEmployeeUniqueness для каждого сотрудника
const mockApiClientInstance = {
checkEmployeeUniqueness: jest
.fn()
.mockResolvedValueOnce({ found: false, message: "No conflict" })
.mockResolvedValueOnce({ found: false, message: "No conflict" }),
createEmployee: jest
.fn()
.mockResolvedValueOnce({ id: "emp1", name: "Иванов Иван" })
.mockResolvedValueOnce({ id: "emp2", name: "Петров Петр" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
const result = await handler.processTelegramMessage(message, "company123", "user123");
expect(result).toEqual({
success: true,
processed: 2,
failed: 0,
duplicates: 0,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
middleName: undefined,
email: "ivan@example.com",
phones: ["+79001234567"],
},
{
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
middleName: undefined,
email: "petr@example.com",
phones: ["+79009876543"],
},
],
errors: undefined,
report: undefined,
});
});
});
describe("MCP Tools", () => {
let mockServer: McpServer;
let handler: EmployeeHandler;
beforeEach(() => {
mockServer = {
tool: jest.fn(),
} as unknown as McpServer;
handler = new EmployeeHandler();
});
it("should handle employee_add_one tool", async () => {
// Мокаем DataProcessor
mockedDataProcessor.parseAdvancedText.mockReturnValue({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft.mockReturnValue({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality.mockReturnValue({ score: 95, issues: [] });
// Мокаем checkEmployeeUniqueness и handleEmployeeConflict
const mockApiClientInstance = {
checkEmployeeUniqueness: jest.fn().mockResolvedValue({ found: false, message: "No conflict" }),
createEmployee: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
handleEmployeeConflict: jest.fn().mockResolvedValue({ action: "create", message: "No conflict" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
// Регистрируем инструменты
registerEmployeeTools(mockServer);
// Получаем зарегистрированный обработчик
const toolCall = (mockServer.tool as jest.Mock).mock.calls.find((call) => call[0] === "employee_add_one");
expect(toolCall).toBeDefined();
const toolHandler = toolCall[3];
const result = await toolHandler({
rawText: "Иванов Иван ivan@example.com",
companyId: "company123",
sessionId: "session123",
});
expect(result).toEqual({
content: [
{
type: "text",
text: expect.stringContaining('"success": true'),
},
],
});
});
it("should handle employee_add_list tool", async () => {
// Мокаем DataProcessor для обработки списка
mockedDataProcessor.parseAdvancedText
.mockReturnValueOnce({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
})
.mockReturnValueOnce({
firstName: "Петр",
lastName: "Петров",
email: "petr@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft
.mockReturnValueOnce({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
})
.mockReturnValueOnce({
companyId: "company123",
lastName: "Петров",
firstName: "Петр",
email: "petr@example.com",
phones: ["+79009876543"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality
.mockReturnValueOnce({ score: 95, issues: [] })
.mockReturnValueOnce({ score: 90, issues: [] });
// Мокаем checkEmployeeUniqueness для каждого сотрудника
const mockApiClientInstance = {
checkEmployeeUniqueness: jest
.fn()
.mockResolvedValueOnce({ found: false, message: "No conflict" })
.mockResolvedValueOnce({ found: false, message: "No conflict" }),
createEmployee: jest
.fn()
.mockResolvedValueOnce({ id: "emp1", name: "Иванов Иван" })
.mockResolvedValueOnce({ id: "emp2", name: "Петров Петр" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
// Регистрируем инструменты
registerEmployeeTools(mockServer);
// Получаем зарегистрированный обработчик
const toolCall = (mockServer.tool as jest.Mock).mock.calls.find((call) => call[0] === "employee_add_list");
expect(toolCall).toBeDefined();
const toolHandler = toolCall[3];
const result = await toolHandler({
rawText: "Иванов Иван ivan@example.com\nПетров Петр petr@example.com",
companyId: "company123",
sessionId: "session123",
});
expect(result).toEqual({
content: [
{
type: "text",
text: expect.stringContaining('"success": true'),
},
],
});
});
it("should handle employee_import_xlsx tool", async () => {
// Мокаем XlsxParser
mockedXlsxParser.parseFromUrl.mockResolvedValue({
success: true,
employees: [
{
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
},
],
totalRows: 1,
validRows: 1,
invalidRows: 0,
duplicates: 0,
errors: [],
report: "✅ Импортировано 1 сотрудников из Excel файла",
});
// Мокаем checkEmployeeUniqueness
const mockApiClientInstance = {
checkEmployeeUniqueness: jest.fn().mockResolvedValue({ found: false, message: "No conflict" }),
createEmployee: jest.fn().mockResolvedValue({ id: "emp1", name: "Иванов Иван" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
// Регистрируем инструменты
registerEmployeeTools(mockServer);
// Получаем зарегистрированный обработчик
const toolCall = (mockServer.tool as jest.Mock).mock.calls.find((call) => call[0] === "employee_import_xlsx");
expect(toolCall).toBeDefined();
const toolHandler = toolCall[3];
const result = await toolHandler({
fileUrl: "https://example.com/employees.xlsx",
companyId: "company123",
sessionId: "session123",
});
expect(result).toEqual({
content: [
{
type: "text",
text: expect.stringContaining('"success": true'),
},
],
});
});
it("should handle employee_telegram_handler tool", async () => {
// Мокаем DataProcessor
mockedDataProcessor.parseAdvancedText.mockReturnValue({
firstName: "Иван",
lastName: "Иванов",
email: "ivan@example.com",
isValid: true,
errors: [],
});
mockedDataProcessor.validateEmployeeDraft.mockReturnValue({
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
phones: ["+79001234567"],
isValid: true,
errors: [],
});
mockedDataProcessor.validateDataQuality.mockReturnValue({ score: 95, issues: [] });
// Мокаем checkEmployeeUniqueness и handleEmployeeConflict
const mockApiClientInstance = {
checkEmployeeUniqueness: jest.fn().mockResolvedValue({ found: false, message: "No conflict" }),
createEmployee: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
handleEmployeeConflict: jest.fn().mockResolvedValue({ action: "create", message: "No conflict" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
// Регистрируем инструменты
registerEmployeeTools(mockServer);
// Получаем зарегистрированный обработчик
const toolCall = (mockServer.tool as jest.Mock).mock.calls.find(
(call) => call[0] === "employee_telegram_handler"
);
expect(toolCall).toBeDefined();
const toolHandler = toolCall[3];
const result = await toolHandler({
message: "Иванов Иван ivan@example.com",
companyId: "company123",
userId: "user123",
sessionId: "session123",
});
expect(result).toEqual({
content: [
{
type: "text",
text: expect.stringContaining('"success": true'),
},
],
});
});
it("should handle employee_resolve_conflict tool", async () => {
// Мокаем API клиент
const mockApiClientInstance = {
createEmployeeForce: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
// Регистрируем инструменты
registerEmployeeTools(mockServer);
// Получаем зарегистрированный обработчик
const toolCall = (mockServer.tool as jest.Mock).mock.calls.find(
(call) => call[0] === "employee_resolve_conflict"
);
expect(toolCall).toBeDefined();
const toolHandler = toolCall[3];
const result = await toolHandler({
employeeData: { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" },
conflictType: "exact",
action: "create",
existingEmployeeId: undefined,
companyId: "company123",
sessionId: "session123",
});
expect(result).toEqual({
content: [
{
type: "text",
text: expect.stringContaining('"success": true'),
},
],
});
});
it("should handle employee_force_create tool", async () => {
// Мокаем API клиент
const mockApiClientInstance = {
createEmployeeForce: jest.fn().mockResolvedValue({ id: "emp123", name: "Иванов Иван" }),
};
mockedEmployeeApiClient.mockImplementation(() => mockApiClientInstance);
// Регистрируем инструменты
registerEmployeeTools(mockServer);
// Получаем зарегистрированный обработчик
const toolCall = (mockServer.tool as jest.Mock).mock.calls.find((call) => call[0] === "employee_force_create");
expect(toolCall).toBeDefined();
const toolHandler = toolCall[3];
const result = await toolHandler({
employeeData: { lastName: "Иванов", firstName: "Иван", email: "ivan@example.com" },
companyId: "company123",
sessionId: "session123",
reason: "Manual override",
});
expect(result).toEqual({
content: [
{
type: "text",
text: expect.stringContaining('"success": true'),
},
],
});
});
});
});