/** * EventController Tests * * Tests for event subscription and publishing endpoints. */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { Test, TestingModule } from "@nestjs/testing"; import { EventController } from "./event.controller"; import { EventService } from "./event.service"; import { AuthGuard } from "../auth/guards/auth.guard"; import { FederationMessageType, FederationMessageStatus } from "@prisma/client"; import type { AuthenticatedRequest } from "../common/types/user.types"; import type { EventMessage, EventAck } from "./types/message.types"; describe("EventController", () => { let controller: EventController; let eventService: EventService; const mockEventService = { subscribeToEventType: vi.fn(), unsubscribeFromEventType: vi.fn(), publishEvent: vi.fn(), getEventSubscriptions: vi.fn(), getEventMessages: vi.fn(), getEventMessage: vi.fn(), handleIncomingEvent: vi.fn(), processEventAck: vi.fn(), }; const mockWorkspaceId = "workspace-123"; const mockUserId = "user-123"; const mockConnectionId = "connection-123"; const mockEventType = "task.created"; beforeEach(async () => { vi.clearAllMocks(); const module: TestingModule = await Test.createTestingModule({ controllers: [EventController], providers: [ { provide: EventService, useValue: mockEventService, }, ], }) .overrideGuard(AuthGuard) .useValue({ canActivate: () => true }) .compile(); controller = module.get(EventController); eventService = module.get(EventService); }); describe("subscribeToEvent", () => { it("should subscribe to an event type", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const dto = { connectionId: mockConnectionId, eventType: mockEventType, metadata: { key: "value" }, }; const mockSubscription = { id: "sub-123", workspaceId: mockWorkspaceId, connectionId: mockConnectionId, eventType: mockEventType, metadata: { key: "value" }, isActive: true, createdAt: new Date(), updatedAt: new Date(), }; mockEventService.subscribeToEventType.mockResolvedValue(mockSubscription); const result = await controller.subscribeToEvent(req, dto); expect(result).toEqual(mockSubscription); expect(mockEventService.subscribeToEventType).toHaveBeenCalledWith( mockWorkspaceId, mockConnectionId, mockEventType, { key: "value" } ); }); it("should throw error if workspace not found", async () => { const req = { user: { id: mockUserId, }, } as AuthenticatedRequest; const dto = { connectionId: mockConnectionId, eventType: mockEventType, }; await expect(controller.subscribeToEvent(req, dto)).rejects.toThrow( "Workspace ID not found in request" ); }); }); describe("unsubscribeFromEvent", () => { it("should unsubscribe from an event type", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const dto = { connectionId: mockConnectionId, eventType: mockEventType, }; mockEventService.unsubscribeFromEventType.mockResolvedValue(undefined); await controller.unsubscribeFromEvent(req, dto); expect(mockEventService.unsubscribeFromEventType).toHaveBeenCalledWith( mockWorkspaceId, mockConnectionId, mockEventType ); }); }); describe("publishEvent", () => { it("should publish an event", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const dto = { eventType: mockEventType, payload: { data: "test" }, }; const mockMessages = [ { id: "msg-123", workspaceId: mockWorkspaceId, connectionId: mockConnectionId, messageType: FederationMessageType.EVENT, messageId: "msg-id-123", eventType: mockEventType, payload: { data: "test" }, status: FederationMessageStatus.DELIVERED, createdAt: new Date(), updatedAt: new Date(), }, ]; mockEventService.publishEvent.mockResolvedValue(mockMessages); const result = await controller.publishEvent(req, dto); expect(result).toEqual(mockMessages); expect(mockEventService.publishEvent).toHaveBeenCalledWith(mockWorkspaceId, mockEventType, { data: "test", }); }); }); describe("getSubscriptions", () => { it("should return all subscriptions for workspace", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const mockSubscriptions = [ { id: "sub-1", workspaceId: mockWorkspaceId, connectionId: mockConnectionId, eventType: "task.created", metadata: {}, isActive: true, createdAt: new Date(), updatedAt: new Date(), }, ]; mockEventService.getEventSubscriptions.mockResolvedValue(mockSubscriptions); const result = await controller.getSubscriptions(req); expect(result).toEqual(mockSubscriptions); expect(mockEventService.getEventSubscriptions).toHaveBeenCalledWith( mockWorkspaceId, undefined ); }); it("should filter by connectionId when provided", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const mockSubscriptions = [ { id: "sub-1", workspaceId: mockWorkspaceId, connectionId: mockConnectionId, eventType: "task.created", metadata: {}, isActive: true, createdAt: new Date(), updatedAt: new Date(), }, ]; mockEventService.getEventSubscriptions.mockResolvedValue(mockSubscriptions); const result = await controller.getSubscriptions(req, mockConnectionId); expect(result).toEqual(mockSubscriptions); expect(mockEventService.getEventSubscriptions).toHaveBeenCalledWith( mockWorkspaceId, mockConnectionId ); }); }); describe("getEventMessages", () => { it("should return all event messages for workspace", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const mockMessages = [ { id: "msg-1", workspaceId: mockWorkspaceId, connectionId: mockConnectionId, messageType: FederationMessageType.EVENT, messageId: "msg-id-1", eventType: "task.created", payload: { data: "test1" }, status: FederationMessageStatus.DELIVERED, createdAt: new Date(), updatedAt: new Date(), }, ]; mockEventService.getEventMessages.mockResolvedValue(mockMessages); const result = await controller.getEventMessages(req); expect(result).toEqual(mockMessages); expect(mockEventService.getEventMessages).toHaveBeenCalledWith(mockWorkspaceId, undefined); }); it("should filter by status when provided", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const mockMessages = [ { id: "msg-1", workspaceId: mockWorkspaceId, connectionId: mockConnectionId, messageType: FederationMessageType.EVENT, messageId: "msg-id-1", eventType: "task.created", payload: { data: "test1" }, status: FederationMessageStatus.PENDING, createdAt: new Date(), updatedAt: new Date(), }, ]; mockEventService.getEventMessages.mockResolvedValue(mockMessages); const result = await controller.getEventMessages(req, FederationMessageStatus.PENDING); expect(result).toEqual(mockMessages); expect(mockEventService.getEventMessages).toHaveBeenCalledWith( mockWorkspaceId, FederationMessageStatus.PENDING ); }); }); describe("getEventMessage", () => { it("should return a single event message", async () => { const req = { user: { id: mockUserId, workspaceId: mockWorkspaceId, }, } as AuthenticatedRequest; const messageId = "msg-123"; const mockMessage = { id: messageId, workspaceId: mockWorkspaceId, connectionId: mockConnectionId, messageType: FederationMessageType.EVENT, messageId: "msg-id-123", eventType: "task.created", payload: { data: "test" }, status: FederationMessageStatus.DELIVERED, createdAt: new Date(), updatedAt: new Date(), }; mockEventService.getEventMessage.mockResolvedValue(mockMessage); const result = await controller.getEventMessage(req, messageId); expect(result).toEqual(mockMessage); expect(mockEventService.getEventMessage).toHaveBeenCalledWith(mockWorkspaceId, messageId); }); }); describe("handleIncomingEvent", () => { it("should handle incoming event and return acknowledgment", async () => { const eventMessage: EventMessage = { messageId: "msg-123", instanceId: "remote-instance-123", eventType: "task.created", payload: { data: "test" }, timestamp: Date.now(), signature: "signature-123", }; const mockAck: EventAck = { messageId: "ack-123", correlationId: eventMessage.messageId, instanceId: "local-instance-123", received: true, timestamp: Date.now(), signature: "ack-signature-123", }; mockEventService.handleIncomingEvent.mockResolvedValue(mockAck); const result = await controller.handleIncomingEvent(eventMessage); expect(result).toEqual(mockAck); expect(mockEventService.handleIncomingEvent).toHaveBeenCalledWith(eventMessage); }); }); describe("handleIncomingEventAck", () => { it("should process event acknowledgment", async () => { const ack: EventAck = { messageId: "ack-123", correlationId: "msg-123", instanceId: "remote-instance-123", received: true, timestamp: Date.now(), signature: "ack-signature-123", }; mockEventService.processEventAck.mockResolvedValue(undefined); const result = await controller.handleIncomingEventAck(ack); expect(result).toEqual({ status: "acknowledged" }); expect(mockEventService.processEventAck).toHaveBeenCalledWith(ack); }); }); });