import EventBus from "../../src/common/event-bus";
describe("EventBus", () => {
beforeEach(() => {
// Clear all listeners before each test
EventBus.removeAllListeners();
});
afterEach(() => {
// Clear all listeners after each test
EventBus.removeAllListeners();
});
describe("event publishing and subscribing", () => {
it("should publish and receive events", (done) => {
const eventName = "test.event";
const eventData = { message: "test data", timestamp: Date.now() };
EventBus.on(eventName, (data: any) => {
expect(data).toEqual(eventData);
done();
});
EventBus.emit(eventName, eventData);
});
it("should handle multiple subscribers", (done) => {
const eventName = "test.multiple";
const eventData = { message: "test data" };
let callCount = 0;
const callback1 = jest.fn(() => {
callCount++;
if (callCount === 2) done();
});
const callback2 = jest.fn(() => {
callCount++;
if (callCount === 2) done();
});
EventBus.on(eventName, callback1);
EventBus.on(eventName, callback2);
EventBus.emit(eventName, eventData);
expect(callback1).toHaveBeenCalledWith(eventData);
expect(callback2).toHaveBeenCalledWith(eventData);
});
it("should handle events with no subscribers", () => {
const eventName = "test.no.subscribers";
const eventData = { message: "test data" };
expect(() => {
EventBus.emit(eventName, eventData);
}).not.toThrow();
});
it("should handle subscriber errors gracefully", () => {
const eventName = "test.error";
const eventData = { message: "test data" };
const errorCallback = jest.fn(() => {
throw new Error("Subscriber error");
});
const successCallback = jest.fn();
EventBus.on(eventName, errorCallback);
EventBus.on(eventName, successCallback);
// EventBus may throw errors from subscribers, which is expected behavior
expect(() => {
EventBus.emit(eventName, eventData);
}).toThrow("Subscriber error");
expect(errorCallback).toHaveBeenCalled();
// successCallback may not be called if errorCallback throws first
});
});
describe("event unsubscribing", () => {
it("should unsubscribe from events", () => {
const eventName = "test.unsubscribe";
const eventData = { message: "test data" };
const callback = jest.fn();
EventBus.on(eventName, callback);
EventBus.off(eventName, callback);
EventBus.emit(eventName, eventData);
expect(callback).not.toHaveBeenCalled();
});
it("should handle unsubscribing non-existent callback", () => {
const eventName = "test.nonexistent";
const callback = jest.fn();
expect(() => {
EventBus.off(eventName, callback);
}).not.toThrow();
});
it("should remove all listeners for an event", () => {
const eventName = "test.remove.all";
const eventData = { message: "test data" };
const callback1 = jest.fn();
const callback2 = jest.fn();
EventBus.on(eventName, callback1);
EventBus.on(eventName, callback2);
EventBus.removeAllListeners(eventName);
EventBus.emit(eventName, eventData);
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
it("should remove all listeners when no event specified", () => {
const eventName1 = "test.remove.all.1";
const eventName2 = "test.remove.all.2";
const eventData = { message: "test data" };
const callback1 = jest.fn();
const callback2 = jest.fn();
EventBus.on(eventName1, callback1);
EventBus.on(eventName2, callback2);
EventBus.removeAllListeners();
EventBus.emit(eventName1, eventData);
EventBus.emit(eventName2, eventData);
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
});
describe("event names and data types", () => {
it("should handle different event name formats", () => {
const events = [
"simple.event",
"complex.event.name",
"event-with-dashes",
"event_with_underscores",
"eventWithCamelCase",
"EVENT.WITH.UPPERCASE",
];
events.forEach((eventName, index) => {
const callback = jest.fn();
EventBus.on(eventName, callback);
EventBus.emit(eventName, { index });
expect(callback).toHaveBeenCalledWith({ index });
});
});
it("should handle different data types", () => {
const eventName = "test.data.types";
const testData = ["string", 123, true, null, undefined, { object: "data" }, ["array", "data"], new Date()];
testData.forEach((data, index) => {
const callback = jest.fn();
EventBus.on(eventName, callback);
EventBus.emit(eventName, data);
expect(callback).toHaveBeenCalledWith(data);
});
});
});
describe("event timing", () => {
it("should handle synchronous event publishing", () => {
const eventName = "test.sync";
const eventData = { message: "sync data" };
const callback = jest.fn();
EventBus.on(eventName, callback);
EventBus.emit(eventName, eventData);
expect(callback).toHaveBeenCalledWith(eventData);
});
it("should handle rapid event publishing", () => {
const eventName = "test.rapid";
const callback = jest.fn();
EventBus.on(eventName, callback);
for (let i = 0; i < 100; i++) {
EventBus.emit(eventName, { index: i });
}
expect(callback).toHaveBeenCalledTimes(100);
});
});
describe("memory management", () => {
it("should not leak memory with many events", () => {
const eventName = "test.memory";
const callback = jest.fn();
EventBus.on(eventName, callback);
// Publish many events
for (let i = 0; i < 1000; i++) {
EventBus.emit(eventName, { index: i });
}
expect(callback).toHaveBeenCalledTimes(1000);
// Clean up
EventBus.off(eventName, callback);
EventBus.emit(eventName, { cleanup: true });
expect(callback).toHaveBeenCalledTimes(1000); // Should not be called after unsubscribe
});
});
describe("EmployeeEventBus specific methods", () => {
let consoleSpy: jest.SpyInstance;
beforeEach(() => {
consoleSpy = jest.spyOn(console, "log").mockImplementation();
});
afterEach(() => {
consoleSpy.mockRestore();
});
it("should emit employee created event", () => {
const event = {
event: "assistant.employee.created" as const,
payload: {
employee: {
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
},
result: { id: "emp123" },
timestamp: "2024-01-01T00:00:00.000Z",
},
};
const callback = jest.fn();
EventBus.on("employee.created", callback);
EventBus.emitEmployeeCreated(event);
expect(consoleSpy).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(event);
});
it("should emit employee error event", () => {
const event = {
event: "mcp.logs.error" as const,
payload: {
error: "Test error",
context: { operation: "test" },
timestamp: "2024-01-01T00:00:00.000Z",
},
};
const callback = jest.fn();
EventBus.on("employee.error", callback);
EventBus.emitEmployeeError(event);
expect(consoleSpy).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(event);
});
it("should emit employee processing event", () => {
const event = {
event: "assistant.employee.processing" as const,
payload: {
action: "add_one" as const,
companyId: "company123",
sessionId: "session123",
userId: "user123",
timestamp: "2024-01-01T00:00:00.000Z",
},
};
const callback = jest.fn();
EventBus.on("employee.processing", callback);
EventBus.emitEmployeeProcessing(event);
expect(consoleSpy).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(event);
});
it("should emit employee validation event", () => {
const event = {
event: "assistant.employee.validation" as const,
payload: {
action: "validation_start" as const,
employeeData: { name: "Test" },
qualityScore: 95,
errors: [],
timestamp: "2024-01-01T00:00:00.000Z",
},
};
const callback = jest.fn();
EventBus.on("employee.validation", callback);
EventBus.emitEmployeeValidation(event);
expect(consoleSpy).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(event);
});
it("should emit employee conflict event", () => {
const event = {
event: "assistant.employee.conflict" as const,
payload: {
conflictType: "exact" as const,
action: "create" as const,
employeeData: { name: "Test" },
existingEmployees: [],
timestamp: "2024-01-01T00:00:00.000Z",
},
};
const callback = jest.fn();
EventBus.on("employee.conflict", callback);
EventBus.emitEmployeeConflict(event);
expect(consoleSpy).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(event);
});
it("should handle broadcast events with logging", () => {
const event = {
event: "assistant.employee.created" as const,
payload: {
employee: {
companyId: "company123",
lastName: "Иванов",
firstName: "Иван",
email: "ivan@example.com",
},
result: { id: "emp123" },
timestamp: "2024-01-01T00:00:00.000Z",
},
};
const callback = jest.fn();
EventBus.on("broadcast", callback);
EventBus.emit("broadcast", event);
expect(consoleSpy).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(event);
});
it("should handle regular emit without logging", () => {
const eventData = { message: "test" };
const callback = jest.fn();
EventBus.on("regular.event", callback);
EventBus.emit("regular.event", eventData);
expect(consoleSpy).not.toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith(eventData);
});
});
describe("EmployeeEventLogger log levels", () => {
let consoleSpy: jest.SpyInstance;
beforeEach(() => {
consoleSpy = jest.spyOn(console, "log").mockImplementation();
});
afterEach(() => {
consoleSpy.mockRestore();
});
it("should log INFO level for employee.created events", () => {
const event = {
event: "assistant.employee.created" as const,
payload: {
employee: { companyId: "test", lastName: "Test", firstName: "Test", email: "test@test.com" },
result: {},
timestamp: "2024-01-01T00:00:00.000Z",
},
};
EventBus.emitEmployeeCreated(event);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('"level":"INFO"'));
});
it("should log DEBUG level for employee.processing events", () => {
const event = {
event: "assistant.employee.processing" as const,
payload: {
action: "add_one" as const,
companyId: "test",
timestamp: "2024-01-01T00:00:00.000Z",
},
};
EventBus.emitEmployeeProcessing(event);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('"level":"DEBUG"'));
});
it("should log DEBUG level for employee.validation events", () => {
const event = {
event: "assistant.employee.validation" as const,
payload: {
action: "validation_start" as const,
employeeData: {},
timestamp: "2024-01-01T00:00:00.000Z",
},
};
EventBus.emitEmployeeValidation(event);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('"level":"DEBUG"'));
});
it("should log WARN level for employee.conflict events", () => {
const event = {
event: "assistant.employee.conflict" as const,
payload: {
conflictType: "exact" as const,
action: "create" as const,
employeeData: {},
timestamp: "2024-01-01T00:00:00.000Z",
},
};
EventBus.emitEmployeeConflict(event);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('"level":"WARN"'));
});
it("should log ERROR level for mcp.logs.error events", () => {
const event = {
event: "mcp.logs.error" as const,
payload: {
error: "Test error",
timestamp: "2024-01-01T00:00:00.000Z",
},
};
EventBus.emitEmployeeError(event);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('"level":"ERROR"'));
});
it("should log INFO level for unknown event types", () => {
const event = {
event: "unknown.event.type" as const,
payload: {
timestamp: "2024-01-01T00:00:00.000Z",
},
};
EventBus.emit("broadcast", event);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('"level":"INFO"'));
});
});
});