diff --git a/backend/src/modules/boards/services/create.board.service.spec.ts b/backend/src/modules/boards/services/create.board.service.spec.ts new file mode 100644 index 000000000..91a976d51 --- /dev/null +++ b/backend/src/modules/boards/services/create.board.service.spec.ts @@ -0,0 +1,596 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import * as CommunicationsType from 'src/modules/communication/interfaces/types'; +import * as Boards from 'src/modules/boards/interfaces/types'; +import * as BoardUsers from 'src/modules/boardUsers/interfaces/types'; +import * as Teams from 'src/modules/teams/interfaces/types'; +import * as TeamUsers from 'src/modules/teamUsers/interfaces/types'; +import * as Schedules from 'src/modules/schedules/interfaces/types'; +import { BoardRepositoryInterface } from '../repositories/board.repository.interface'; +import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { CreateBoardServiceInterface } from '../interfaces/services/create.board.service.interface'; +import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface'; +import { GetTeamUserServiceInterface } from 'src/modules/teamUsers/interfaces/services/get.team.user.service.interface'; +import { UpdateTeamUserServiceInterface } from 'src/modules/teamUsers/interfaces/services/update.team.user.service.interface'; +import { CreateSchedulesServiceInterface } from 'src/modules/schedules/interfaces/services/create.schedules.service.interface'; +import { BoardDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock'; +import faker from '@faker-js/faker'; +import { CreateFailedException } from 'src/libs/exceptions/createFailedBadRequestException'; +import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory.mock'; +import BoardDto from '../dto/board.dto'; +import { BoardUserFactory } from 'src/libs/test-utils/mocks/factories/boardUser-factory.mock'; +import { CreateBoardUserServiceInterface } from 'src/modules/boardUsers/interfaces/services/create.board.user.service.interface'; +import { TeamFactory } from 'src/libs/test-utils/mocks/factories/team-factory.mock'; +import { TeamUserFactory } from 'src/libs/test-utils/mocks/factories/teamUser-factory.mock'; +import { UserFactory } from 'src/libs/test-utils/mocks/factories/user-factory'; +import { BoardUserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock'; +import CreateBoardService from 'src/modules/boards/services/create.board.service'; +import Team from 'src/modules/teams/entities/team.schema'; +import Board from 'src/modules/boards/entities/board.schema'; +import BoardUser from 'src/modules/boardUsers/entities/board.user.schema'; +import User from 'src/modules/users/entities/user.schema'; +import TeamUser from 'src/modules/teamUsers/entities/team.user.schema'; +import { Configs } from '../dto/configs.dto'; +import { BadRequestException, NotFoundException } from '@nestjs/common'; +import { TeamRoles } from 'src/libs/enum/team.roles'; + +const userId: string = faker.datatype.uuid(); + +const team: Team = TeamFactory.create(); + +const boardDataWithDividedBoard: BoardDto = BoardDtoFactory.create({ + team: team._id, + isSubBoard: false, + recurrent: true, + maxUsers: 2, + dividedBoards: BoardDtoFactory.createMany(2, [ + { isSubBoard: true, boardNumber: 1 }, + { isSubBoard: true, boardNumber: 2 } + ]) +}); + +const subBoardsResult: Board[] = BoardFactory.createMany(2, [ + { + isSubBoard: true, + boardNumber: 1, + title: (boardDataWithDividedBoard.dividedBoards[0] as BoardDto).title + }, + { + isSubBoard: true, + boardNumber: 2, + title: (boardDataWithDividedBoard.dividedBoards[0] as BoardDto).title + } +]); +const subBoardUsers: BoardUser[] = BoardUserFactory.createMany(8, [ + { board: subBoardsResult[0]._id }, + { board: subBoardsResult[0]._id }, + { board: subBoardsResult[0]._id }, + { board: subBoardsResult[0]._id }, + { board: subBoardsResult[1]._id }, + { board: subBoardsResult[1]._id }, + { board: subBoardsResult[1]._id }, + { board: subBoardsResult[1]._id } +]); + +const users: User[] = UserFactory.createMany(9, [ + { _id: subBoardUsers[0].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[1].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[2].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[3].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[4].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[5].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[6].user as string, providerAccountCreatedAt: faker.date.past(1) }, + { _id: subBoardUsers[7].user as string, providerAccountCreatedAt: new Date() }, + {} +]); + +const teamUsers: TeamUser[] = TeamUserFactory.createMany(9, [ + { + team: team._id, + user: users[0], + isNewJoiner: true, + canBeResponsible: false, + role: TeamRoles.MEMBER + }, + { + team: team._id, + user: users[1], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: users[2], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: users[3], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: users[4], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: users[5], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: users[6], + role: TeamRoles.ADMIN, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: users[7], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: users[8], + role: TeamRoles.STAKEHOLDER, + isNewJoiner: false, + canBeResponsible: true + } +]); + +team.users = teamUsers; + +const boardCreated: Board = BoardFactory.create({ + isSubBoard: false, + dividedBoards: subBoardsResult, + team: team._id +}); + +const boardUsers: BoardUser[] = BoardUserFactory.createMany(9, [ + { board: boardCreated._id, user: subBoardUsers[0].user }, + { board: boardCreated._id, user: subBoardUsers[1].user }, + { board: boardCreated._id, user: subBoardUsers[2].user }, + { board: boardCreated._id, user: subBoardUsers[3].user }, + { board: boardCreated._id, user: subBoardUsers[4].user }, + { board: boardCreated._id, user: subBoardUsers[5].user }, + { board: boardCreated._id, user: subBoardUsers[6].user }, + { board: boardCreated._id, user: subBoardUsers[7].user }, + { board: boardCreated._id, user: users[8] } +]); + +const usersRegularBoard = BoardUserDtoFactory.createMany(4); + +const boardDataRegularBoard = BoardDtoFactory.create({ + team: null, + isSubBoard: false, + users: usersRegularBoard +}); + +const configs: Configs = { + recurrent: faker.datatype.boolean(), + maxVotes: undefined, + hideCards: faker.datatype.boolean(), + hideVotes: faker.datatype.boolean(), + maxUsersPerTeam: 2, + slackEnable: faker.datatype.boolean(), + date: faker.datatype.datetime(), + postAnonymously: faker.datatype.boolean() +}; + +describe('CreateBoardService', () => { + let boardService: CreateBoardServiceInterface; + let boardRepositoryMock: DeepMocked; + let createBoardUserServiceMock: DeepMocked; + let getTeamServiceMock: DeepMocked; + let getTeamUserServiceMock: DeepMocked; + let updateTeamUserServiceMock: DeepMocked; + let createSchedulesServiceMock: DeepMocked; + let slackCommunicationServiceMock: DeepMocked; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CreateBoardService, + { + provide: Teams.TYPES.services.GetTeamService, + useValue: createMock() + }, + { + provide: TeamUsers.TYPES.services.GetTeamUserService, + useValue: createMock() + }, + { + provide: TeamUsers.TYPES.services.UpdateTeamUserService, + useValue: createMock() + }, + { + provide: Schedules.TYPES.services.CreateSchedulesService, + useValue: createMock() + }, + { + provide: CommunicationsType.TYPES.services.SlackCommunicationService, + useValue: createMock() + }, + { + provide: Boards.TYPES.repositories.BoardRepository, + useValue: createMock() + }, + { + provide: BoardUsers.TYPES.services.CreateBoardUserService, + useValue: createMock() + } + ] + }).compile(); + + boardService = module.get(CreateBoardService); + boardRepositoryMock = module.get(Boards.TYPES.repositories.BoardRepository); + createBoardUserServiceMock = module.get(BoardUsers.TYPES.services.CreateBoardUserService); + getTeamServiceMock = module.get(Teams.TYPES.services.GetTeamService); + getTeamUserServiceMock = module.get(TeamUsers.TYPES.services.GetTeamUserService); + updateTeamUserServiceMock = module.get(TeamUsers.TYPES.services.UpdateTeamUserService); + createSchedulesServiceMock = module.get(Schedules.TYPES.services.CreateSchedulesService); + slackCommunicationServiceMock = module.get( + CommunicationsType.TYPES.services.SlackCommunicationService + ); + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + + getTeamServiceMock.getTeam.mockResolvedValue(team); + boardRepositoryMock.create.mockResolvedValue(boardCreated); + getTeamUserServiceMock.getUsersOfTeam.mockResolvedValue(teamUsers); + boardRepositoryMock.commitTransaction.mockResolvedValue(null); + }); + + const generateTeamXgeeksData = (slackEnable = false) => { + const teamXgeeks = TeamFactory.create({ name: 'xgeeks' }); + + boardDataWithDividedBoard.team = teamXgeeks._id; + boardDataWithDividedBoard.slackEnable = slackEnable; + + const xgeeksBoardCreated = BoardFactory.create({ + isSubBoard: false, + dividedBoards: subBoardsResult, + team: teamXgeeks._id + }); + const xgeeksBoardUsers = BoardUserFactory.createMany(9, [ + { board: xgeeksBoardCreated._id, user: subBoardUsers[0].user }, + { board: xgeeksBoardCreated._id, user: subBoardUsers[1].user }, + { board: xgeeksBoardCreated._id, user: subBoardUsers[2].user }, + { board: xgeeksBoardCreated._id, user: subBoardUsers[3].user }, + { board: xgeeksBoardCreated._id, user: subBoardUsers[4].user }, + { board: xgeeksBoardCreated._id, user: subBoardUsers[5].user }, + { board: xgeeksBoardCreated._id, user: subBoardUsers[6].user }, + { board: xgeeksBoardCreated._id, user: subBoardUsers[7].user }, + { board: xgeeksBoardCreated._id, user: users[8] } + ]); + const teamUsersXgeeks = TeamUserFactory.createMany(9, [ + { team: teamXgeeks._id, user: users[0] }, + { team: teamXgeeks._id, user: users[1] }, + { team: teamXgeeks._id, user: users[2] }, + { team: teamXgeeks._id, user: users[3] }, + { team: teamXgeeks._id, user: users[4] }, + { team: teamXgeeks._id, user: users[5] }, + { team: teamXgeeks._id, user: users[6] }, + { team: teamXgeeks._id, user: users[7] }, + { team: teamXgeeks._id, user: users[8] } + ]); + + teamXgeeks.users = teamUsersXgeeks; + + //mocks the creation of a split board and it's boardUsers with the xgeeks + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(subBoardUsers); + boardRepositoryMock.create.mockResolvedValue(xgeeksBoardCreated); + + //mocks the team name and the team users + getTeamServiceMock.getTeam.mockResolvedValue(teamXgeeks); + getTeamUserServiceMock.getUsersOfTeam.mockResolvedValue(teamUsersXgeeks); + + //saves the board users of a main board + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(xgeeksBoardUsers); + }; + + it('should be defined', () => { + expect(boardService).toBeDefined(); + }); + + describe('create', () => { + it("should throw an error if a board with divided boards isn't created", async () => { + createBoardUserServiceMock.saveBoardUsers.mockResolvedValue(boardUsers); + boardRepositoryMock.create.mockResolvedValue(null); + + expect( + async () => await boardService.create(boardDataWithDividedBoard, userId) + ).rejects.toThrow(CreateFailedException); + }); + + it("should throw an error if a board without divided boards isn't created", () => { + boardRepositoryMock.create.mockResolvedValue(null); + + expect(async () => await boardService.create(boardDataRegularBoard, userId)).rejects.toThrow( + CreateFailedException + ); + }); + + it('should create a board with divided boards', async () => { + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(subBoardUsers); + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(boardUsers); + + const createdBoardResult = await boardService.create(boardDataWithDividedBoard, userId); + + expect(getTeamServiceMock.getTeam).toBeCalledTimes(1); + expect(getTeamUserServiceMock.getUsersOfTeam).toBeCalledTimes(1); + + /*Should be called when: + - creating the users for the subBoards + - creating the users for the main board + */ + expect(createBoardUserServiceMock.saveBoardUsers).toBeCalledTimes(2); + expect(createdBoardResult).toEqual(boardCreated); + }); + + it("should throw an error if the team isn't found on the getTeamNameAndTeamUsers function", async () => { + createBoardUserServiceMock.saveBoardUsers.mockResolvedValue(subBoardUsers); + getTeamServiceMock.getTeam.mockResolvedValue(null); + + expect( + async () => await boardService.create(boardDataWithDividedBoard, userId) + ).rejects.toThrow(CreateFailedException); + }); + + it("should throw an error if the team users aren't found on the saveBoardUsersFromTeam function", async () => { + createBoardUserServiceMock.saveBoardUsers.mockResolvedValue(subBoardUsers); + getTeamUserServiceMock.getUsersOfTeam.mockResolvedValue(null); + + expect( + async () => await boardService.create(boardDataWithDividedBoard, userId) + ).rejects.toThrow(CreateFailedException); + }); + + it('should throw an error if the createBoardUserService.saveBoardUsers function fails', async () => { + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(subBoardUsers); + createBoardUserServiceMock.saveBoardUsers.mockRejectedValueOnce( + 'error inserting board users' + ); + + expect( + async () => await boardService.create(boardDataWithDividedBoard, userId) + ).rejects.toThrow(CreateFailedException); + }); + + it('should create a board without divided boards', async () => { + const regularBoardCreated = BoardFactory.create({ + isSubBoard: false, + team: null + }); + const regularBoardUsers = BoardUserFactory.createMany(4, [ + { board: regularBoardCreated._id, user: usersRegularBoard[0].user }, + { board: regularBoardCreated._id, user: usersRegularBoard[1].user }, + { board: regularBoardCreated._id, user: usersRegularBoard[2].user }, + { board: regularBoardCreated._id, user: usersRegularBoard[3].user } + ]); + + boardRepositoryMock.create.mockResolvedValue(regularBoardCreated); + createBoardUserServiceMock.saveBoardUsers.mockResolvedValue(regularBoardUsers); + + const createdBoardResult = await boardService.create(boardDataRegularBoard, userId); + + /*Should be called when: + - creating the users for the main board + */ + expect(createBoardUserServiceMock.saveBoardUsers).toBeCalledTimes(1); + expect(createdBoardResult).toEqual(regularBoardCreated); + }); + + it('should call the createSchedulesService.addCronJob function if the board is recurrent and the teamName is xgeeks', async () => { + generateTeamXgeeksData(); + + await boardService.create(boardDataWithDividedBoard, userId); + + expect(createSchedulesServiceMock.addCronJob).toBeCalledTimes(1); + }); + + it('should call the slackCommunicationService.execute function if the board has slack enable and the teamName is xgeeks', async () => { + generateTeamXgeeksData(true); + + boardRepositoryMock.getBoardPopulated.mockResolvedValueOnce(boardCreated); + + await boardService.create(boardDataWithDividedBoard, userId); + + expect(slackCommunicationServiceMock.execute).toBeCalledTimes(1); + }); + + it('should throw an error if one of the commit transactions fails', async () => { + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(subBoardUsers); + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(boardUsers); + boardRepositoryMock.commitTransaction.mockRejectedValue('commit transaction failed'); + + expect( + async () => await boardService.create(boardDataWithDividedBoard, userId) + ).rejects.toThrow(CreateFailedException); + }); + }); + + describe('splitBoardByTeam', () => { + it("should throw an error when the team users aren't found", async () => { + getTeamUserServiceMock.getUsersOfTeam.mockResolvedValue(null); + expect( + async () => await boardService.splitBoardByTeam(userId, team._id, configs, team.name) + ).rejects.toThrow(NotFoundException); + }); + + it('should return the id of the created board', async () => { + updateTeamUserServiceMock.updateTeamUser.mockResolvedValue(teamUsers[0]); + + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(subBoardUsers); + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(boardUsers); + + const boardIdResult = await boardService.splitBoardByTeam( + userId, + team._id, + configs, + team.name + ); + + expect(boardIdResult).toEqual(boardCreated._id); + }); + + it('should throw error when the maxTeams or maxUsersPerTeam are lower than 2 ', async () => { + const configs_2: Configs = { + recurrent: faker.datatype.boolean(), + maxVotes: undefined, + hideCards: faker.datatype.boolean(), + hideVotes: faker.datatype.boolean(), + maxUsersPerTeam: 1, + slackEnable: faker.datatype.boolean(), + date: faker.datatype.datetime(), + postAnonymously: faker.datatype.boolean() + }; + updateTeamUserServiceMock.updateTeamUser.mockResolvedValue(teamUsers[0]); + + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(subBoardUsers); + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(boardUsers); + + expect( + async () => await boardService.splitBoardByTeam(userId, team._id, configs_2, team.name) + ).rejects.toThrow(BadRequestException); + }); + + it('should call the updateTeamUserService.updateTeamUser function', async () => { + const newTeam = TeamFactory.create(); + + const newUsers: User[] = UserFactory.createMany(9, [ + { + _id: subBoardUsers[0].user as string, + providerAccountCreatedAt: faker.date.between( + '2022-02-02T00:00:00.000Z', + '2022-09-02T00:00:00.000Z' + ) + }, + { _id: subBoardUsers[1].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[2].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[3].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[4].user as string, providerAccountCreatedAt: new Date() }, + { _id: subBoardUsers[5].user as string, providerAccountCreatedAt: new Date() }, + { + _id: subBoardUsers[6].user as string, + providerAccountCreatedAt: faker.date.between( + '2022-02-02T00:00:00.000Z', + '2022-09-02T00:00:00.000Z' + ) + }, + { _id: subBoardUsers[7].user as string, providerAccountCreatedAt: new Date() }, + {} + ]); + const newTeamUsers: TeamUser[] = TeamUserFactory.createMany(9, [ + { + team: team._id, + user: newUsers[0], + isNewJoiner: true, + canBeResponsible: false, + role: TeamRoles.MEMBER + }, + { + team: team._id, + user: newUsers[1], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: newUsers[2], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: newUsers[3], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: newUsers[4], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: newUsers[5], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: newUsers[6], + role: TeamRoles.ADMIN, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: newUsers[7], + role: TeamRoles.MEMBER, + isNewJoiner: true, + canBeResponsible: false + }, + { + team: team._id, + user: newUsers[8], + role: TeamRoles.STAKEHOLDER, + isNewJoiner: false, + canBeResponsible: true + } + ]); + newTeam.users = newTeamUsers; + + const newBoardCreated: Board = BoardFactory.create({ + isSubBoard: false, + dividedBoards: subBoardsResult, + team: team._id + }); + + const newBoardUsers: BoardUser[] = BoardUserFactory.createMany(9, [ + { board: newBoardCreated._id, user: subBoardUsers[0].user }, + { board: newBoardCreated._id, user: subBoardUsers[1].user }, + { board: newBoardCreated._id, user: subBoardUsers[2].user }, + { board: newBoardCreated._id, user: subBoardUsers[3].user }, + { board: newBoardCreated._id, user: subBoardUsers[4].user }, + { board: newBoardCreated._id, user: subBoardUsers[5].user }, + { board: newBoardCreated._id, user: subBoardUsers[6].user }, + { board: newBoardCreated._id, user: subBoardUsers[7].user }, + { board: newBoardCreated._id, user: users[8] } + ]); + + getTeamServiceMock.getTeam.mockResolvedValue(newTeam); + updateTeamUserServiceMock.updateTeamUser.mockResolvedValue(newTeamUsers[0]); + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(subBoardUsers); + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(newBoardUsers); + getTeamUserServiceMock.getUsersOfTeam.mockResolvedValue(newTeamUsers); + boardRepositoryMock.create.mockResolvedValue(newBoardCreated); + + await boardService.splitBoardByTeam(userId, newTeam._id, configs, newTeam.name); + + expect(updateTeamUserServiceMock.updateTeamUser).toBeCalled(); + }); + }); +}); diff --git a/backend/src/modules/boards/services/create.board.service.ts b/backend/src/modules/boards/services/create.board.service.ts index 21b475718..88588d7a7 100644 --- a/backend/src/modules/boards/services/create.board.service.ts +++ b/backend/src/modules/boards/services/create.board.service.ts @@ -27,11 +27,23 @@ import { CreateBoardServiceInterface } from '../interfaces/services/create.board import Board from '../entities/board.schema'; import { addDays, addMonths, isAfter } from 'date-fns'; import { BoardRepositoryInterface } from '../repositories/board.repository.interface'; -import { Inject, Injectable, Logger, NotFoundException, forwardRef } from '@nestjs/common'; +import { + BadRequestException, + Inject, + Injectable, + Logger, + NotFoundException, + forwardRef +} from '@nestjs/common'; import { Configs } from '../dto/configs.dto'; import { TEAM_NOT_FOUND, TEAM_USERS_NOT_FOUND } from 'src/libs/exceptions/messages'; import { CreateFailedException } from 'src/libs/exceptions/createFailedBadRequestException'; +type CreateBoardAndUsers = { + boardData: BoardDto; + userId: string; +}; + @Injectable() export default class CreateBoardService implements CreateBoardServiceInterface { private logger = new Logger(CreateBoardService.name); @@ -54,43 +66,18 @@ export default class CreateBoardService implements CreateBoardServiceInterface { ) {} async create(boardData: BoardDto, userId: string, fromSchedule = false): Promise { - const { team: teamId, recurrent, maxUsers, slackEnable, users, dividedBoards } = boardData; + const { team: teamId, recurrent, maxUsers, slackEnable } = boardData; - const boardUsersToCreate: BoardUserDto[] = []; - const haveDividedBoards = dividedBoards.length > 0; - - let createdBoard; - let teamName; + const createBoardArgs: CreateBoardAndUsers = { + boardData, + userId + }; await this.boardRepository.startTransaction(); await this.createBoardUserService.startTransaction(); try { - try { - createdBoard = await this.createBoard(boardData, userId, false, haveDividedBoards); - - if (teamId) { - teamName = await this.getTeamNameAndTeamUsers( - teamId, - boardUsersToCreate, - boardData.responsibles - ); - } - - if (!haveDividedBoards && !teamId) { - this.saveParticipantsFromRegularBoardWithNoTeam(users, boardUsersToCreate); - } - - await this.createBoardUserService.saveBoardUsers( - boardUsersToCreate, - createdBoard._id, - true - ); - } catch (e) { - await this.boardRepository.abortTransaction(); - await this.createBoardUserService.abortTransaction(); - throw new CreateFailedException(); - } + const { createdBoard, teamName } = await this.createBoardAndSaveBoardUsers(createBoardArgs); await this.boardRepository.commitTransaction(); await this.createBoardUserService.commitTransaction(); @@ -127,57 +114,59 @@ export default class CreateBoardService implements CreateBoardServiceInterface { configs: Configs, teamName: string ): Promise { - const { maxUsersPerTeam } = configs; - let teamUsers = await this.getTeamUserService.getUsersOfTeam(teamId); if (!teamUsers) throw new NotFoundException(TEAM_USERS_NOT_FOUND); teamUsers = this.updateTeamUserNewJoinerOrResponsibleStatus(teamUsers, teamId); - const teamUsersWotStakeholders = teamUsers.filter( - (teamUser) => teamUser.role !== TeamRoles.STAKEHOLDER - ); - const teamLength = teamUsersWotStakeholders.length; - - const rawMaxTeams = teamLength / Number(maxUsersPerTeam); - const maxTeams = Math.ceil(rawMaxTeams); + const boardData = this.generateBoardData(teamUsers, configs, teamName, teamId); - if (maxTeams < 2 || maxUsersPerTeam < 2) { - return null; + if (!boardData) { + throw new BadRequestException(); } - const responsibles = []; - const today = new Date(); - - const boardData: BoardDto = { - ...generateBoardDtoData( - `${teamName}-mainboard-${new Intl.DateTimeFormat('en-US', { - month: 'long' - }).format(today)}-${configs.date?.getFullYear()}` - ).board, - users: [], - team: teamId, - dividedBoards: this.handleSplitBoards(maxTeams, teamUsersWotStakeholders, responsibles), - recurrent: configs.recurrent, - maxVotes: configs.maxVotes ?? null, - hideCards: true, - postAnonymously: configs.postAnonymously, - hideVotes: configs.hideVotes ?? false, - maxUsers: Math.ceil(configs.maxUsersPerTeam), - slackEnable: configs.slackEnable, - responsibles - }; - const board = await this.create(boardData, ownerId, true); - if (!board) return null; - return board._id.toString(); } // /* --------------- HELPERS --------------- */ + private async createBoardAndSaveBoardUsers({ boardData, userId }: CreateBoardAndUsers) { + const { team: teamId, users, dividedBoards } = boardData; + const boardUsersToCreate: BoardUserDto[] = []; + const haveDividedBoards = dividedBoards.length > 0; + + try { + let teamName: string | undefined; + const createdBoard = await this.createBoard(boardData, userId, false, haveDividedBoards); + + if (teamId) { + teamName = await this.getTeamNameAndTeamUsers( + teamId, + boardUsersToCreate, + boardData.responsibles + ); + } + + if (!haveDividedBoards && !teamId) { + this.saveParticipantsFromRegularBoardWithNoTeam(users, boardUsersToCreate); + } + + await this.createBoardUserService.saveBoardUsers(boardUsersToCreate, createdBoard._id, true); + + return { + createdBoard, + teamName + }; + } catch (e) { + await this.boardRepository.abortTransaction(); + await this.createBoardUserService.abortTransaction(); + throw new CreateFailedException(); + } + } + private async getTeamNameAndTeamUsers( teamId: string, newUsers: BoardUserDto[], @@ -225,6 +214,7 @@ export default class CreateBoardService implements CreateBoardServiceInterface { const board = fillDividedBoardsUsersWithTeamUsers(translateBoard(populatedBoard)); this.slackCommunicationService.execute(board); } else { + //this isn't tested on the create.board.service.spec.ts this.logger.error( `Call Slack Communication Service for board id "${boardId}" fails. Board not found.` ); @@ -270,6 +260,47 @@ export default class CreateBoardService implements CreateBoardServiceInterface { }); } + private generateBoardData( + teamUsers: TeamUser[], + configs: Configs, + teamName: string, + teamId: string + ): BoardDto { + const teamUsersWotStakeholders = teamUsers.filter( + (teamUser) => teamUser.role !== TeamRoles.STAKEHOLDER + ); + const teamLength = teamUsersWotStakeholders.length; + + const rawMaxTeams = teamLength / Number(configs.maxUsersPerTeam); + const maxTeams = Math.ceil(rawMaxTeams); + + if (maxTeams < 2 || configs.maxUsersPerTeam < 2) { + return null; + } + + const responsibles = []; + const today = new Date(); + + return { + ...generateBoardDtoData( + `${teamName}-mainboard-${new Intl.DateTimeFormat('en-US', { + month: 'long' + }).format(today)}-${configs.date?.getFullYear()}` + ).board, + users: [], + team: teamId, + dividedBoards: this.handleSplitBoards(maxTeams, teamUsersWotStakeholders, responsibles), + recurrent: configs.recurrent, + maxVotes: configs.maxVotes ?? null, + hideCards: true, + postAnonymously: configs.postAnonymously, + hideVotes: configs.hideVotes ?? false, + maxUsers: Math.ceil(configs.maxUsersPerTeam), + slackEnable: configs.slackEnable, + responsibles + }; + } + private handleBoardUserRole(teamUser: TeamUser): string { return teamUser.role === TeamRoles.ADMIN || teamUser.role === TeamRoles.STAKEHOLDER ? BoardRoles.RESPONSIBLE @@ -302,6 +333,7 @@ export default class CreateBoardService implements CreateBoardServiceInterface { canBeResponsible: true }); + //this isn't tested on the create.board.service.spec.ts if (!updatedUser) { this.logger.verbose( `Update isNewJoiner and can be responsible fields failed for ${user._id}` @@ -317,8 +349,8 @@ export default class CreateBoardService implements CreateBoardServiceInterface { }); } - private sortUsersListByOldestCreatedDate = (users: TeamUser[]) => - users + private sortUsersListByOldestCreatedDate = (users: TeamUser[]) => { + return users .map((user) => { return { ...user, @@ -326,6 +358,7 @@ export default class CreateBoardService implements CreateBoardServiceInterface { }; }) .sort((a, b) => Number(b.userCreated) - Number(a.userCreated)); + }; private getAvailableUsersToBeResponsible = (availableUsers: TeamUser[]) => { const availableUsersListSorted = this.sortUsersListByOldestCreatedDate(availableUsers); @@ -407,6 +440,7 @@ export default class CreateBoardService implements CreateBoardServiceInterface { const canBeResponsibles = availableUsers.filter( (user) => !user.isNewJoiner && user.canBeResponsible ); + const responsiblesAvailable: TeamUser[] = []; while (canBeResponsibles.length > 0 && responsiblesAvailable.length !== maxTeams) { const idx = Math.floor(Math.random() * canBeResponsibles.length); diff --git a/backend/src/modules/boards/services/update.board.service.spec.ts b/backend/src/modules/boards/services/update.board.service.spec.ts index 0b7b33791..466d4fdbc 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -18,9 +18,7 @@ import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory. import { UpdateBoardDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/updateBoardDto-factory.mock'; import { BoardUserFactory } from 'src/libs/test-utils/mocks/factories/boardUser-factory.mock'; import { NotFoundException } from '@nestjs/common'; -import { BoardUserRepositoryInterface } from 'src/modules/boardUsers/interfaces/repositories/board-user.repository.interface'; import { - boardUserRepository, createBoardUserService, deleteBoardUserService, getBoardUserService, @@ -46,7 +44,7 @@ import { TeamCommunicationDtoFactory } from 'src/libs/test-utils/mocks/factories import { UpdateFailedException } from 'src/libs/exceptions/updateFailedBadRequestException'; import { DeepMocked, createMock } from '@golevelup/ts-jest'; -describe('GetUpdateBoardService', () => { +describe('UpdateBoardService', () => { let boardService: UpdateBoardServiceInterface; let updateBoardUserServiceMock: DeepMocked; let boardRepositoryMock: DeepMocked; @@ -97,10 +95,6 @@ describe('GetUpdateBoardService', () => { provide: boardRepository.provide, useValue: createMock() }, - { - provide: boardUserRepository.provide, - useValue: createMock() - }, { provide: SocketGateway, useValue: createMock() diff --git a/backend/src/modules/schedules/services/create.schedules.service.ts b/backend/src/modules/schedules/services/create.schedules.service.ts index 88205381e..9fa7c40a5 100644 --- a/backend/src/modules/schedules/services/create.schedules.service.ts +++ b/backend/src/modules/schedules/services/create.schedules.service.ts @@ -165,26 +165,25 @@ export class CreateSchedulesService implements CreateSchedulesServiceInterface { const team = oldBoard.team as Team; - const boardId = await this.createBoardService.splitBoardByTeam( - ownerId, - teamId, - configs, - team.name - ); - - if (!boardId) { - await this.deleteSchedulesService.deleteScheduleByBoardId(oldBoardId); - - return; - } + try { + const boardId = await this.createBoardService.splitBoardByTeam( + ownerId, + teamId, + configs, + team.name + ); - const addCronJobDto: AddCronJobDto = { - ownerId, - teamId, - boardId: boardId ?? oldBoardId, - maxUsersPerTeam: deletedSchedule.maxUsers - }; + const addCronJobDto: AddCronJobDto = { + ownerId, + teamId, + boardId: boardId ?? oldBoardId, + maxUsersPerTeam: deletedSchedule.maxUsers + }; - this.addCronJob({ day, month, addCronJobDto }); + this.addCronJob({ day, month, addCronJobDto }); + } catch (e) { + this.logger.error(e); + await this.deleteSchedulesService.deleteScheduleByBoardId(oldBoardId); + } } } diff --git a/backend/src/modules/teamUsers/services/update.team.user.service.ts b/backend/src/modules/teamUsers/services/update.team.user.service.ts index 11bef1ed0..a77e92534 100644 --- a/backend/src/modules/teamUsers/services/update.team.user.service.ts +++ b/backend/src/modules/teamUsers/services/update.team.user.service.ts @@ -19,12 +19,8 @@ export default class UpdateTeamUserService implements UpdateTeamUserServiceInter private deleteTeamUserService: DeleteTeamUserServiceInterface ) {} - async updateTeamUser(teamUserData: TeamUserDto): Promise { - const teamUserSaved = await this.teamUserRepository.updateTeamUser(teamUserData); - - if (!teamUserSaved) throw new BadRequestException(UPDATE_FAILED); - - return teamUserSaved; + updateTeamUser(teamUserData: TeamUserDto): Promise { + return this.teamUserRepository.updateTeamUser(teamUserData); } async addAndRemoveTeamUsers(addUsers: TeamUserDto[], removeUsers: string[]): Promise {