All checks were successful
ci/woodpecker/push/api Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
287 lines
8.6 KiB
TypeScript
287 lines
8.6 KiB
TypeScript
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>(TeamsService);
|
|
prisma = module.get<PrismaService>(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);
|
|
});
|
|
});
|
|
});
|