import { describe, expect, it, vi, beforeEach } from "vitest"; import { BrainService } from "./brain.service"; import { PrismaService } from "../prisma/prisma.service"; import { TaskStatus, TaskPriority, ProjectStatus, EntityType } from "@prisma/client"; describe("BrainService", () => { let service: BrainService; let mockPrisma: { task: { findMany: ReturnType; count: ReturnType; }; event: { findMany: ReturnType; count: ReturnType; }; project: { findMany: ReturnType; count: ReturnType; }; workspace: { findUniqueOrThrow: ReturnType; }; }; const mockWorkspaceId = "123e4567-e89b-12d3-a456-426614174000"; const mockTasks = [ { id: "task-1", title: "Test Task 1", description: "Description 1", status: TaskStatus.IN_PROGRESS, priority: TaskPriority.HIGH, dueDate: new Date("2025-02-01"), assignee: { id: "user-1", name: "John Doe", email: "john@example.com" }, project: { id: "project-1", name: "Project 1", color: "#ff0000" }, }, { id: "task-2", title: "Test Task 2", description: null, status: TaskStatus.NOT_STARTED, priority: TaskPriority.MEDIUM, dueDate: null, assignee: null, project: null, }, ]; const mockEvents = [ { id: "event-1", title: "Test Event 1", description: "Event description", startTime: new Date("2025-02-01T10:00:00Z"), endTime: new Date("2025-02-01T11:00:00Z"), allDay: false, location: "Conference Room A", project: { id: "project-1", name: "Project 1", color: "#ff0000" }, }, ]; const mockProjects = [ { id: "project-1", name: "Project 1", description: "Project description", status: ProjectStatus.ACTIVE, startDate: new Date("2025-01-01"), endDate: new Date("2025-06-30"), color: "#ff0000", _count: { tasks: 5, events: 3 }, }, ]; beforeEach(() => { mockPrisma = { task: { findMany: vi.fn().mockResolvedValue(mockTasks), count: vi.fn().mockResolvedValue(10), }, event: { findMany: vi.fn().mockResolvedValue(mockEvents), count: vi.fn().mockResolvedValue(5), }, project: { findMany: vi.fn().mockResolvedValue(mockProjects), count: vi.fn().mockResolvedValue(3), }, workspace: { findUniqueOrThrow: vi.fn().mockResolvedValue({ id: mockWorkspaceId, name: "Test Workspace", }), }, }; service = new BrainService(mockPrisma as unknown as PrismaService); }); describe("query", () => { it("should query all entity types by default", async () => { const result = await service.query({ workspaceId: mockWorkspaceId, }); expect(result.tasks).toHaveLength(2); expect(result.events).toHaveLength(1); expect(result.projects).toHaveLength(1); expect(result.meta.totalTasks).toBe(2); expect(result.meta.totalEvents).toBe(1); expect(result.meta.totalProjects).toBe(1); }); it("should query only specified entity types", async () => { const result = await service.query({ workspaceId: mockWorkspaceId, entities: [EntityType.TASK], }); expect(result.tasks).toHaveLength(2); expect(result.events).toHaveLength(0); expect(result.projects).toHaveLength(0); expect(mockPrisma.task.findMany).toHaveBeenCalled(); expect(mockPrisma.event.findMany).not.toHaveBeenCalled(); expect(mockPrisma.project.findMany).not.toHaveBeenCalled(); }); it("should apply task filters", async () => { await service.query({ workspaceId: mockWorkspaceId, tasks: { status: TaskStatus.IN_PROGRESS, priority: TaskPriority.HIGH, }, }); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ workspaceId: mockWorkspaceId, status: TaskStatus.IN_PROGRESS, priority: TaskPriority.HIGH, }), }) ); }); it("should apply task statuses filter (array)", async () => { await service.query({ workspaceId: mockWorkspaceId, tasks: { statuses: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS], }, }); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ status: { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] }, }), }) ); }); it("should apply overdue filter", async () => { await service.query({ workspaceId: mockWorkspaceId, tasks: { overdue: true, }, }); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ dueDate: expect.objectContaining({ lt: expect.any(Date) }), status: { in: [TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS] }, }), }) ); }); it("should apply unassigned filter", async () => { await service.query({ workspaceId: mockWorkspaceId, tasks: { unassigned: true, }, }); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ assigneeId: null, }), }) ); }); it("should apply due date range filter", async () => { const dueDateFrom = new Date("2025-01-01"); const dueDateTo = new Date("2025-01-31"); await service.query({ workspaceId: mockWorkspaceId, tasks: { dueDateFrom, dueDateTo, }, }); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ dueDate: { gte: dueDateFrom, lte: dueDateTo }, }), }) ); }); it("should apply event filters", async () => { await service.query({ workspaceId: mockWorkspaceId, events: { allDay: true, upcoming: true, }, }); expect(mockPrisma.event.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ allDay: true, startTime: { gte: expect.any(Date) }, }), }) ); }); it("should apply event date range filter", async () => { const startFrom = new Date("2025-02-01"); const startTo = new Date("2025-02-28"); await service.query({ workspaceId: mockWorkspaceId, events: { startFrom, startTo, }, }); expect(mockPrisma.event.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ startTime: { gte: startFrom, lte: startTo }, }), }) ); }); it("should apply project filters", async () => { await service.query({ workspaceId: mockWorkspaceId, projects: { status: ProjectStatus.ACTIVE, }, }); expect(mockPrisma.project.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ status: ProjectStatus.ACTIVE, }), }) ); }); it("should apply project statuses filter (array)", async () => { await service.query({ workspaceId: mockWorkspaceId, projects: { statuses: [ProjectStatus.PLANNING, ProjectStatus.ACTIVE], }, }); expect(mockPrisma.project.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ status: { in: [ProjectStatus.PLANNING, ProjectStatus.ACTIVE] }, }), }) ); }); it("should apply search term across tasks", async () => { await service.query({ workspaceId: mockWorkspaceId, search: "test", entities: [EntityType.TASK], }); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ OR: [ { title: { contains: "test", mode: "insensitive" } }, { description: { contains: "test", mode: "insensitive" } }, ], }), }) ); }); it("should apply search term across events", async () => { await service.query({ workspaceId: mockWorkspaceId, search: "conference", entities: [EntityType.EVENT], }); expect(mockPrisma.event.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ OR: [ { title: { contains: "conference", mode: "insensitive" } }, { description: { contains: "conference", mode: "insensitive" } }, { location: { contains: "conference", mode: "insensitive" } }, ], }), }) ); }); it("should apply search term across projects", async () => { await service.query({ workspaceId: mockWorkspaceId, search: "project", entities: [EntityType.PROJECT], }); expect(mockPrisma.project.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: expect.objectContaining({ OR: [ { name: { contains: "project", mode: "insensitive" } }, { description: { contains: "project", mode: "insensitive" } }, ], }), }) ); }); it("should respect limit parameter", async () => { await service.query({ workspaceId: mockWorkspaceId, limit: 5, }); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ take: 5, }) ); }); it("should include query and filters in meta", async () => { const result = await service.query({ workspaceId: mockWorkspaceId, query: "What tasks are due?", tasks: { status: TaskStatus.IN_PROGRESS }, }); expect(result.meta.query).toBe("What tasks are due?"); expect(result.meta.filters.tasks).toEqual({ status: TaskStatus.IN_PROGRESS }); }); }); describe("getContext", () => { it("should return context with summary", async () => { const result = await service.getContext({ workspaceId: mockWorkspaceId, }); expect(result.timestamp).toBeInstanceOf(Date); expect(result.workspace.id).toBe(mockWorkspaceId); expect(result.workspace.name).toBe("Test Workspace"); expect(result.summary).toEqual({ activeTasks: 10, overdueTasks: 10, upcomingEvents: 5, activeProjects: 3, }); }); it("should include tasks when requested", async () => { const result = await service.getContext({ workspaceId: mockWorkspaceId, includeTasks: true, }); expect(result.tasks).toBeDefined(); expect(result.tasks).toHaveLength(2); expect(result.tasks![0].isOverdue).toBeDefined(); }); it("should include events when requested", async () => { const result = await service.getContext({ workspaceId: mockWorkspaceId, includeEvents: true, }); expect(result.events).toBeDefined(); expect(result.events).toHaveLength(1); }); it("should include projects when requested", async () => { const result = await service.getContext({ workspaceId: mockWorkspaceId, includeProjects: true, }); expect(result.projects).toBeDefined(); expect(result.projects).toHaveLength(1); expect(result.projects![0].taskCount).toBeDefined(); }); it("should use custom eventDays", async () => { await service.getContext({ workspaceId: mockWorkspaceId, eventDays: 14, }); expect(mockPrisma.event.count).toHaveBeenCalled(); expect(mockPrisma.event.findMany).toHaveBeenCalled(); }); it("should not include tasks when explicitly disabled", async () => { const result = await service.getContext({ workspaceId: mockWorkspaceId, includeTasks: false, includeEvents: true, includeProjects: true, }); expect(result.tasks).toBeUndefined(); expect(result.events).toBeDefined(); expect(result.projects).toBeDefined(); }); it("should not include events when explicitly disabled", async () => { const result = await service.getContext({ workspaceId: mockWorkspaceId, includeTasks: true, includeEvents: false, includeProjects: true, }); expect(result.tasks).toBeDefined(); expect(result.events).toBeUndefined(); expect(result.projects).toBeDefined(); }); it("should not include projects when explicitly disabled", async () => { const result = await service.getContext({ workspaceId: mockWorkspaceId, includeTasks: true, includeEvents: true, includeProjects: false, }); expect(result.tasks).toBeDefined(); expect(result.events).toBeDefined(); expect(result.projects).toBeUndefined(); }); }); describe("search", () => { it("should search across all entities", async () => { const result = await service.search(mockWorkspaceId, "test"); expect(result.tasks).toHaveLength(2); expect(result.events).toHaveLength(1); expect(result.projects).toHaveLength(1); expect(result.meta.query).toBe("test"); }); it("should respect limit parameter", async () => { await service.search(mockWorkspaceId, "test", 5); expect(mockPrisma.task.findMany).toHaveBeenCalledWith( expect.objectContaining({ take: 5, }) ); }); it("should handle empty search term", async () => { const result = await service.search(mockWorkspaceId, ""); expect(result.tasks).toBeDefined(); expect(result.events).toBeDefined(); expect(result.projects).toBeDefined(); }); }); });