import { BadRequestException, ConflictException, NotFoundException } from "@nestjs/common"; import { Test, TestingModule } from "@nestjs/testing"; import { TeamMemberRole } from "@prisma/client"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { PrismaService } from "../prisma/prisma.service"; import { TeamsService } from "./teams.service"; describe("TeamsService", () => { let service: TeamsService; let prisma: PrismaService; const mockPrismaService = { team: { create: vi.fn(), findMany: vi.fn(), findFirst: vi.fn(), deleteMany: vi.fn(), }, workspaceMember: { findUnique: vi.fn(), }, teamMember: { findUnique: vi.fn(), create: vi.fn(), deleteMany: vi.fn(), }, }; const mockWorkspaceId = "550e8400-e29b-41d4-a716-446655440001"; const mockTeamId = "550e8400-e29b-41d4-a716-446655440002"; const mockUserId = "550e8400-e29b-41d4-a716-446655440003"; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ TeamsService, { provide: PrismaService, useValue: mockPrismaService, }, ], }).compile(); service = module.get(TeamsService); prisma = module.get(PrismaService); vi.clearAllMocks(); }); it("should be defined", () => { expect(service).toBeDefined(); }); describe("create", () => { it("should create a team", async () => { const createDto = { name: "Platform Team", description: "Owns platform services", }; const createdTeam = { id: mockTeamId, workspaceId: mockWorkspaceId, name: createDto.name, description: createDto.description, metadata: {}, createdAt: new Date(), updatedAt: new Date(), }; mockPrismaService.team.create.mockResolvedValue(createdTeam); const result = await service.create(mockWorkspaceId, createDto); expect(result).toEqual(createdTeam); expect(prisma.team.create).toHaveBeenCalledWith({ data: { workspaceId: mockWorkspaceId, name: createDto.name, description: createDto.description, }, }); }); }); describe("findAll", () => { it("should list teams for a workspace", async () => { const teams = [ { id: mockTeamId, workspaceId: mockWorkspaceId, name: "Platform Team", description: "Owns platform services", metadata: {}, createdAt: new Date(), updatedAt: new Date(), _count: { members: 1 }, }, ]; mockPrismaService.team.findMany.mockResolvedValue(teams); const result = await service.findAll(mockWorkspaceId); expect(result).toEqual(teams); expect(prisma.team.findMany).toHaveBeenCalledWith({ where: { workspaceId: mockWorkspaceId }, include: { _count: { select: { members: true }, }, }, orderBy: { createdAt: "asc" }, }); }); }); describe("addMember", () => { it("should add a workspace member to a team", async () => { const dto = { userId: mockUserId, role: TeamMemberRole.ADMIN, }; const createdTeamMember = { teamId: mockTeamId, userId: mockUserId, role: TeamMemberRole.ADMIN, joinedAt: new Date(), user: { id: mockUserId, name: "Test User", email: "test@example.com", }, }; mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId }); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ userId: mockUserId }); mockPrismaService.teamMember.findUnique.mockResolvedValue(null); mockPrismaService.teamMember.create.mockResolvedValue(createdTeamMember); const result = await service.addMember(mockWorkspaceId, mockTeamId, dto); expect(result).toEqual(createdTeamMember); expect(prisma.team.findFirst).toHaveBeenCalledWith({ where: { id: mockTeamId, workspaceId: mockWorkspaceId, }, select: { id: true }, }); expect(prisma.workspaceMember.findUnique).toHaveBeenCalledWith({ where: { workspaceId_userId: { workspaceId: mockWorkspaceId, userId: mockUserId, }, }, select: { userId: true }, }); expect(prisma.teamMember.create).toHaveBeenCalledWith({ data: { teamId: mockTeamId, userId: mockUserId, role: TeamMemberRole.ADMIN, }, include: { user: { select: { id: true, name: true, email: true, }, }, }, }); }); it("should use MEMBER role when role is omitted", async () => { const dto = { userId: mockUserId }; mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId }); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ userId: mockUserId }); mockPrismaService.teamMember.findUnique.mockResolvedValue(null); mockPrismaService.teamMember.create.mockResolvedValue({ teamId: mockTeamId, userId: mockUserId, role: TeamMemberRole.MEMBER, joinedAt: new Date(), }); await service.addMember(mockWorkspaceId, mockTeamId, dto); expect(prisma.teamMember.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ role: TeamMemberRole.MEMBER, }), }) ); }); it("should throw when team does not belong to workspace", async () => { mockPrismaService.team.findFirst.mockResolvedValue(null); await expect( service.addMember(mockWorkspaceId, mockTeamId, { userId: mockUserId }) ).rejects.toThrow(NotFoundException); expect(prisma.workspaceMember.findUnique).not.toHaveBeenCalled(); }); it("should throw when user is not a workspace member", async () => { mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId }); mockPrismaService.workspaceMember.findUnique.mockResolvedValue(null); await expect( service.addMember(mockWorkspaceId, mockTeamId, { userId: mockUserId }) ).rejects.toThrow(BadRequestException); }); it("should throw when user is already in the team", async () => { mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId }); mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ userId: mockUserId }); mockPrismaService.teamMember.findUnique.mockResolvedValue({ userId: mockUserId }); await expect( service.addMember(mockWorkspaceId, mockTeamId, { userId: mockUserId }) ).rejects.toThrow(ConflictException); }); }); describe("removeMember", () => { it("should remove a member from a team", async () => { mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId }); mockPrismaService.teamMember.deleteMany.mockResolvedValue({ count: 1 }); await service.removeMember(mockWorkspaceId, mockTeamId, mockUserId); expect(prisma.teamMember.deleteMany).toHaveBeenCalledWith({ where: { teamId: mockTeamId, userId: mockUserId, }, }); }); it("should throw when team does not belong to workspace", async () => { mockPrismaService.team.findFirst.mockResolvedValue(null); await expect(service.removeMember(mockWorkspaceId, mockTeamId, mockUserId)).rejects.toThrow( NotFoundException ); expect(prisma.teamMember.deleteMany).not.toHaveBeenCalled(); }); it("should throw when user is not in the team", async () => { mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId }); mockPrismaService.teamMember.deleteMany.mockResolvedValue({ count: 0 }); await expect(service.removeMember(mockWorkspaceId, mockTeamId, mockUserId)).rejects.toThrow( NotFoundException ); }); }); describe("remove", () => { it("should delete a team", async () => { mockPrismaService.team.deleteMany.mockResolvedValue({ count: 1 }); await service.remove(mockWorkspaceId, mockTeamId); expect(prisma.team.deleteMany).toHaveBeenCalledWith({ where: { id: mockTeamId, workspaceId: mockWorkspaceId, }, }); }); it("should throw when team is not found", async () => { mockPrismaService.team.deleteMany.mockResolvedValue({ count: 0 }); await expect(service.remove(mockWorkspaceId, mockTeamId)).rejects.toThrow(NotFoundException); }); }); });