From 5085197186bc2df783eca4fde093e801a4608f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Mon, 13 Mar 2023 14:48:10 +0000 Subject: [PATCH 01/14] feat: add dto factories --- .../mocks/factories/board-factory.mock.ts | 4 +- .../factories/dto/boardDto-factory.mock.ts | 42 +++++++++++++++++++ .../factories/dto/cardDto-factory.mock.ts | 21 ++++++++++ .../factories/dto/cardItemDto-factory.mock.ts | 19 +++++++++ .../factories/dto/columnDto-factory.mock.ts | 26 ++++++++++++ .../factories/dto/commentsDto-factory.mock.ts | 15 +++++++ .../dto/updateBoardDto-factory.mock.ts | 15 +++++++ ...Dto-factory.ts => userDto-factory.mock.ts} | 0 8 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/updateBoardDto-factory.mock.ts rename backend/src/libs/test-utils/mocks/factories/dto/{userDto-factory.ts => userDto-factory.mock.ts} (100%) diff --git a/backend/src/libs/test-utils/mocks/factories/board-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/board-factory.mock.ts index ee24f7782..9fca1bc1d 100644 --- a/backend/src/libs/test-utils/mocks/factories/board-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/board-factory.mock.ts @@ -3,8 +3,6 @@ import { ColumnFactory } from './column-factory.mock'; import Board from 'src/modules/boards/entities/board.schema'; import { buildTestFactory } from './generic-factory.mock'; -const userId = faker.datatype.uuid(); - const mockBoardData = () => { return { _id: faker.database.mongodbObjectId(), @@ -26,7 +24,7 @@ const mockBoardData = () => { slackEnable: faker.datatype.boolean(), addCards: faker.datatype.boolean(), responsibles: ['1'], - createdBy: userId, + createdBy: faker.datatype.uuid(), addcards: faker.datatype.boolean(), postAnonymously: faker.datatype.boolean(), createdAt: faker.datatype.datetime().toISOString() diff --git a/backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts new file mode 100644 index 000000000..6c09886da --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts @@ -0,0 +1,42 @@ +import faker from '@faker-js/faker'; +import { BoardPhases } from 'src/libs/enum/board.phases'; +import BoardDto from 'src/modules/boards/dto/board.dto'; +import { buildTestFactory } from '../generic-factory.mock'; +import { columnDtoFactory } from './columnDto-factory.mock'; + +const mockBoardDto = () => { + return { + _id: faker.database.mongodbObjectId(), + title: faker.lorem.words(), + columns: columnDtoFactory.createMany(3), + isPublic: faker.datatype.boolean(), + maxVotes: faker.datatype.number({ min: 0, max: 6 }), + maxUsers: 0, + maxTeams: '1', + hideCards: faker.datatype.boolean(), + hideVotes: faker.datatype.boolean(), + dividedBoards: [], + team: '1', + socketId: faker.datatype.uuid(), + users: [], + recurrent: faker.datatype.boolean(), + isSubBoard: faker.datatype.boolean(), + boardNumber: 0, + slackEnable: faker.datatype.boolean(), + addCards: faker.datatype.boolean(), + responsibles: ['1'], + createdBy: faker.datatype.uuid(), + addcards: faker.datatype.boolean(), + postAnonymously: faker.datatype.boolean(), + createdAt: faker.datatype.datetime().toISOString(), + phase: faker.helpers.arrayElement([ + BoardPhases.ADDCARDS, + BoardPhases.SUBMITTED, + BoardPhases.VOTINGPHASE + ]) + }; +}; + +export const BoardDtoFactory = buildTestFactory(() => { + return mockBoardDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts new file mode 100644 index 000000000..ab525a890 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts @@ -0,0 +1,21 @@ +import faker from '@faker-js/faker'; +import CardDto from 'src/modules/cards/dto/card.dto'; +import { buildTestFactory } from '../generic-factory.mock'; +import { cardItemDtoFactory } from './cardItemDto-factory.mock'; +import { commentDtoFactory } from './commentsDto-factory.mock'; + +const mockCardDto = () => { + return { + items: [cardItemDtoFactory.create()], + id: faker.database.mongodbObjectId(), + text: faker.lorem.words(), + createdBy: faker.datatype.uuid(), + comments: [commentDtoFactory.create()], + votes: [], + anonymous: faker.datatype.boolean() + }; +}; + +export const cardDtoFactory = buildTestFactory(() => { + return mockCardDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts new file mode 100644 index 000000000..b0feebb66 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts @@ -0,0 +1,19 @@ +import faker from '@faker-js/faker'; +import CardItemDto from 'src/modules/cards/dto/card.item.dto'; +import { buildTestFactory } from '../generic-factory.mock'; +import { commentDtoFactory } from './commentsDto-factory.mock'; + +const mockCardItemDto = () => { + return { + id: faker.database.mongodbObjectId(), + text: faker.lorem.words(), + createdBy: faker.datatype.uuid(), + comments: [commentDtoFactory.create()], + votes: [], + anonymous: faker.datatype.boolean() + }; +}; + +export const cardItemDtoFactory = buildTestFactory(() => { + return mockCardItemDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts new file mode 100644 index 000000000..11e9edd23 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts @@ -0,0 +1,26 @@ +import faker from '@faker-js/faker'; +import ColumnDto from 'src/modules/columns/dto/column.dto'; +import { buildTestFactory } from '../generic-factory.mock'; +import { cardDtoFactory } from './cardDto-factory.mock'; + +const mockColumnDto = () => { + return { + _id: faker.database.mongodbObjectId(), + title: faker.lorem.words(), + color: faker.helpers.arrayElement([ + '#CDFAE0', + '#DEB7FF', + '#9BFDFA', + '#FE9EBF', + '#9DCAFF', + '#FEB9A9' + ]), + cards: [cardDtoFactory.create()], + cardText: faker.lorem.words(), + isDefaultText: faker.datatype.boolean() + }; +}; + +export const columnDtoFactory = buildTestFactory(() => { + return mockColumnDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts new file mode 100644 index 000000000..f6ed00c1c --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts @@ -0,0 +1,15 @@ +import faker from '@faker-js/faker'; +import CommentDto from 'src/modules/comments/dto/comment.dto'; +import { buildTestFactory } from '../generic-factory.mock'; + +const mockCommentDto = () => { + return { + text: faker.lorem.words(), + createdBy: faker.datatype.uuid(), + anonymous: faker.datatype.boolean() + }; +}; + +export const commentDtoFactory = buildTestFactory(() => { + return mockCommentDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/updateBoardDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/updateBoardDto-factory.mock.ts new file mode 100644 index 000000000..c2f8ed755 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/updateBoardDto-factory.mock.ts @@ -0,0 +1,15 @@ +import { UpdateBoardDto } from 'src/modules/boards/dto/update-board.dto'; +import { BoardUserFactory } from '../boardUser-factory.mock'; +import { buildTestFactory } from '../generic-factory.mock'; +import { BoardDtoFactory } from './boardDto-factory.mock'; + +const mockUpdateBoardDto = () => { + return { + responsible: BoardUserFactory.create(), + ...BoardDtoFactory.create() + }; +}; + +export const UpdateBoardDtoFactory = buildTestFactory(() => { + return mockUpdateBoardDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/userDto-factory.ts b/backend/src/libs/test-utils/mocks/factories/dto/userDto-factory.mock.ts similarity index 100% rename from backend/src/libs/test-utils/mocks/factories/dto/userDto-factory.ts rename to backend/src/libs/test-utils/mocks/factories/dto/userDto-factory.mock.ts From 6401734ed3532eb26a71a3ac5d8b517ed1495138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Mon, 13 Mar 2023 14:48:56 +0000 Subject: [PATCH 02/14] refactor: split update board function --- .../applications/update.board.application.ts | 4 +- .../boards/controller/boards.controller.ts | 16 +- .../update.board.application.interface.ts | 6 +- .../update.board.service.interface.ts | 6 +- .../board.repository.interface.ts | 4 +- .../boards/repositories/board.repository.ts | 13 +- .../boards/services/update.board.service.ts | 167 ++++++++++++------ 7 files changed, 140 insertions(+), 76 deletions(-) diff --git a/backend/src/modules/boards/applications/update.board.application.ts b/backend/src/modules/boards/applications/update.board.application.ts index b36446b33..9efdab036 100644 --- a/backend/src/modules/boards/applications/update.board.application.ts +++ b/backend/src/modules/boards/applications/update.board.application.ts @@ -17,8 +17,8 @@ export class UpdateBoardApplication implements UpdateBoardApplicationInterface { return this.updateBoardService.update(boardId, boardData); } - mergeBoards(subBoardId: string, userId: string) { - return this.updateBoardService.mergeBoards(subBoardId, userId); + mergeBoards(subBoardId: string, userId: string, socketId?: string) { + return this.updateBoardService.mergeBoards(subBoardId, userId, socketId); } updateBoardParticipants(boardData: UpdateBoardUserDto) { diff --git a/backend/src/modules/boards/controller/boards.controller.ts b/backend/src/modules/boards/controller/boards.controller.ts index 59bae858e..00d1e1560 100644 --- a/backend/src/modules/boards/controller/boards.controller.ts +++ b/backend/src/modules/boards/controller/boards.controller.ts @@ -36,7 +36,7 @@ import { import { BaseParam } from 'src/libs/dto/param/base.param'; import { PaginationParams } from 'src/libs/dto/param/pagination.params'; import { BaseParamWSocket } from 'src/libs/dto/param/socket.param'; -import { BOARD_NOT_FOUND, INSERT_FAILED, UPDATE_FAILED } from 'src/libs/exceptions/messages'; +import { BOARD_NOT_FOUND, INSERT_FAILED } from 'src/libs/exceptions/messages'; import JwtAuthenticationGuard from 'src/libs/guards/jwtAuth.guard'; import RequestWithUser from 'src/libs/interfaces/requestWithUser.interface'; import { BadRequestResponse } from 'src/libs/swagger/errors/bad-request.swagger'; @@ -328,22 +328,12 @@ export default class BoardsController { type: InternalServerErrorResponse }) @Put(':boardId/merge') - async mergeBoard( + mergeBoard( @Param() { boardId }: BaseParam, @Query() { socketId }: BaseParamWSocket, @Req() request: RequestWithUser ) { - const result = await this.updateBoardApp.mergeBoards(boardId, request.user._id); - - if (!result) { - throw new BadRequestException(UPDATE_FAILED); - } - - if (socketId) { - this.socketService.sendUpdatedAllBoard(boardId, socketId); - } - - return result; + return this.updateBoardApp.mergeBoards(boardId, request.user._id, socketId); } @ApiOperation({ summary: 'Update board phase' }) diff --git a/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts b/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts index ce5fe99a8..024281a93 100644 --- a/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts +++ b/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts @@ -8,7 +8,11 @@ import { BoardPhaseDto } from 'src/libs/dto/board-phase.dto'; export interface UpdateBoardApplicationInterface { update(boardId: string, boardData: UpdateBoardDto): Promise | null>; - mergeBoards(subBoardId: string, userId: string): Promise | null>; + mergeBoards( + subBoardId: string, + userId: string, + socketId?: string + ): Promise | null>; updateBoardParticipants(boardData: UpdateBoardUserDto): Promise; diff --git a/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts b/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts index 47c7dd923..238dcac30 100644 --- a/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts +++ b/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts @@ -9,7 +9,11 @@ import { BoardPhaseDto } from 'src/libs/dto/board-phase.dto'; export interface UpdateBoardServiceInterface { update(boardId: string, boardData: UpdateBoardDto): Promise | null>; - mergeBoards(subBoardId: string, userId: string): Promise | null>; + mergeBoards( + subBoardId: string, + userId: string, + socketId?: string + ): Promise | null>; updateChannelId(teams: TeamDto[]); updateBoardParticipants( diff --git a/backend/src/modules/boards/repositories/board.repository.interface.ts b/backend/src/modules/boards/repositories/board.repository.interface.ts index f2ff6ed8a..ad8ba74e8 100644 --- a/backend/src/modules/boards/repositories/board.repository.interface.ts +++ b/backend/src/modules/boards/repositories/board.repository.interface.ts @@ -30,8 +30,8 @@ export interface BoardRepositoryInterface extends BaseInterfaceRepository ): Promise; deleteBoard(boardId: string, withSession: boolean): Promise; updateBoard(boardId: string, board: Board, isNew: boolean): Promise; - updateMergedSubBoard(subBoardId: string, userId: string): Promise; - updateMergedBoard(boardId: string, newColumns: Column[]): Promise; + updateMergedSubBoard(subBoardId: string, userId: string, withSession: boolean): Promise; + updateMergedBoard(boardId: string, newColumns: Column[], withSession: boolean): Promise; updatedChannelId(boardId: string, channelId: string): Promise; updatePhase(boardId: string, phase: BoardPhases): Promise; } diff --git a/backend/src/modules/boards/repositories/board.repository.ts b/backend/src/modules/boards/repositories/board.repository.ts index ca3c0c096..187385149 100644 --- a/backend/src/modules/boards/repositories/board.repository.ts +++ b/backend/src/modules/boards/repositories/board.repository.ts @@ -147,7 +147,7 @@ export class BoardRepository ); } - updateMergedSubBoard(subBoardId: string, userId: string) { + updateMergedSubBoard(subBoardId: string, userId: string, withSession: boolean) { return this.findOneByFieldAndUpdate( { _id: subBoardId @@ -157,11 +157,14 @@ export class BoardRepository submitedByUser: userId, submitedAt: new Date() } - } + }, + null, + null, + withSession ); } - updateMergedBoard(boardId: string, newColumns: Column[]) { + updateMergedBoard(boardId: string, newColumns: Column[], withSession: boolean) { return this.findOneByFieldAndUpdate( { _id: boardId @@ -169,7 +172,9 @@ export class BoardRepository { $set: { columns: newColumns } }, - { new: true } + { new: true }, + null, + withSession ); } diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index 67281caac..70be3943f 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -95,42 +95,22 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { */ if (boardData.users && String(currentResponsible?.id) !== String(newResponsible?.id)) { if (isSubBoard) { - const promises = boardData.users - .filter((boardUser) => - [getIdFromObjectId(String(currentResponsible?.id)), String(newResponsible.id)].includes( - (boardUser.user as unknown as User)._id - ) - ) - .map((boardUser) => { - const typedBoardUser = boardUser.user as unknown as User; - - return this.updateBoardUserService.updateBoardUserRole( - boardId, - typedBoardUser._id, - boardUser.role - ); - }); - await Promise.all(promises); + this.updateBoardUsersRole( + boardId, + boardData.users, + String(currentResponsible.id), + String(newResponsible.id) + ); } const mainBoardId = boardData.mainBoardId; - const promises = boardData.users - .filter((boardUser) => - [getIdFromObjectId(String(currentResponsible?.id)), newResponsible.id].includes( - (boardUser.user as unknown as User)._id - ) - ) - .map((boardUser) => { - const typedBoardUser = boardUser.user as unknown as User; - - return this.updateBoardUserService.updateBoardUserRole( - mainBoardId, - typedBoardUser._id, - boardUser.role - ); - }); - await Promise.all(promises); + this.updateBoardUsersRole( + mainBoardId, + boardData.users, + String(currentResponsible.id), + String(newResponsible.id) + ); } /** @@ -164,7 +144,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { if (!isEmpty(boardData.maxVotes)) { const highestVotes = await this.getHighestVotesOnBoard(boardId); - // TODO: maxVotes as 'undefined' not undefined (so typeof returns string, but needs to be number or undefined) if (highestVotes > Number(boardData.maxVotes)) { throw new BadRequestException( `You can't set a lower value to max votes. Please insert a value higher or equals than ${highestVotes}!` @@ -201,39 +180,55 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { return updatedBoard; } - async mergeBoards(subBoardId: string, userId: string) { + async mergeBoards(subBoardId: string, userId: string, socketId?: string) { const [subBoard, board] = await Promise.all([ this.boardRepository.getBoard(subBoardId), this.boardRepository.getBoardByQuery({ dividedBoards: { $in: [subBoardId] } }) ]); - if (!subBoard || !board || subBoard.submitedByUser) return null; + if (!subBoard || !board || subBoard.submitedByUser) + throw new BadRequestException(UPDATE_FAILED); + const team = await this.getTeamService.getTeam((board.team as ObjectId).toString()); - if (!team) return null; + if (!team) throw new BadRequestException(UPDATE_FAILED); - const newSubColumns = this.generateNewSubColumns(subBoard as Board); + const columnsWitMergedCards = this.getColumnsFromMainBoardWithMergedCards(subBoard, board); - const newColumns = [...(board as Board).columns]; - for (let i = 0; i < newColumns.length; i++) { - newColumns[i].cards = [...newColumns[i].cards, ...newSubColumns[i].cards]; - } + await this.boardRepository.startTransaction(); + try { + await this.boardRepository.updateMergedSubBoard(subBoardId, userId, true); + const result = await this.boardRepository.updateMergedBoard( + board._id, + columnsWitMergedCards, + true + ); - await this.boardRepository.updateMergedSubBoard(subBoardId, userId); + if (board.slackChannelId && board.slackEnable) { + this.slackCommunicationService.executeMergeBoardNotification({ + responsiblesChannelId: board.slackChannelId, + teamNumber: subBoard.boardNumber, + isLastSubBoard: await this.checkIfIsLastBoardToMerge(result.dividedBoards as Board[]), + boardId: subBoardId, + mainBoardId: board._id + }); + } - const result = await this.boardRepository.updateMergedBoard(board._id, newColumns); + if (socketId) { + this.socketService.sendUpdatedAllBoard(subBoardId, socketId); + } - if (board.slackChannelId && board.slackEnable) { - this.slackCommunicationService.executeMergeBoardNotification({ - responsiblesChannelId: board.slackChannelId, - teamNumber: subBoard.boardNumber, - isLastSubBoard: await this.checkIfIsLastBoardToMerge(result.dividedBoards as Board[]), - boardId: subBoardId, - mainBoardId: board._id - }); + await this.boardRepository.commitTransaction(); + await this.boardRepository.endSession(); + + return result; + } catch (e) { + await this.boardRepository.abortTransaction(); + } finally { + await this.boardRepository.endSession(); } - return result; + throw new BadRequestException(UPDATE_FAILED); } updateChannelId(teams: TeamDto[]) { @@ -318,6 +313,39 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { return { id: user._id, email: user.email }; } + /** + * Method to update all boardUsers role + * @param boardId String + * @param boardUsers BoardUserDto[] + * @param currentResponsibleId String + * @param newResponsibleId String + * @return void + * @private + */ + private async updateBoardUsersRole( + boardId: string, + boardUsers: BoardUserDto[], + currentResponsibleId: string, + newResponsibleId: string + ) { + const promises = boardUsers + .filter((boardUser) => + [getIdFromObjectId(currentResponsibleId), newResponsibleId].includes( + (boardUser.user as unknown as User)._id + ) + ) + .map((boardUser) => { + const typedBoardUser = boardUser.user as unknown as User; + + return this.boardUserRepository.updateBoardUserRole( + boardId, + typedBoardUser._id, + boardUser.role + ); + }); + await Promise.all(promises); + } + /** * Method to get the highest value of votesCount on Board Users * @param boardId String @@ -408,11 +436,18 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { return count === dividedBoards.length; } + /** + * Method to generate columns from sub-board + * @param subBoard: Board + * @return Column[] + */ private generateNewSubColumns(subBoard: Board) { return [...subBoard.columns].map((column) => { const newColumn = { title: column.title, color: column.color, + cardText: column.cardText, + isDefaultText: column.isDefaultText, cards: column.cards.map((card) => { const newCard = { text: card.text, @@ -451,10 +486,36 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { }) }; - return newColumn; + return newColumn as Column; }); } + /** + * Method to merge cards from sub-board into a main board + * @param columns: Column[] + * @param subColumns: Column[] + * @return Column[] + */ + private mergeCardsFromSubBoardColumnsIntoMainBoard(columns: Column[], subColumns: Column[]) { + for (let i = 0; i < columns.length; i++) { + columns[i].cards = [...columns[i].cards, ...subColumns[i].cards]; + } + + return columns; + } + + /** + * Method to return columns with cards merged cards a from sub-board + * @param subBoard: Board + * @param board: Board + * @return Column[] + */ + private getColumnsFromMainBoardWithMergedCards(subBoard: Board, board: Board) { + const newSubColumns = this.generateNewSubColumns(subBoard); + + return this.mergeCardsFromSubBoardColumnsIntoMainBoard([...board.columns], newSubColumns); + } + private generateMessage(phase: string, boardId: string, date: string, columns): string { const createdAt = new Date(date); const month = createdAt.toLocaleString('default', { From b58c93ab0b4474b258def89aecebff2effae1e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Mon, 13 Mar 2023 14:49:34 +0000 Subject: [PATCH 03/14] test: add file update board service spec --- .../boards/services/get.board.service.spec.ts | 2 +- .../services/update.board.service.spec.ts | 157 +++++++----------- 2 files changed, 58 insertions(+), 101 deletions(-) diff --git a/backend/src/modules/boards/services/get.board.service.spec.ts b/backend/src/modules/boards/services/get.board.service.spec.ts index 8107dc374..950d0c6b3 100644 --- a/backend/src/modules/boards/services/get.board.service.spec.ts +++ b/backend/src/modules/boards/services/get.board.service.spec.ts @@ -16,7 +16,7 @@ import * as Auth from 'src/modules/auth/interfaces/types'; import faker from '@faker-js/faker'; import { BoardUserFactory } from 'src/libs/test-utils/mocks/factories/boardUser-factory.mock'; import { TeamFactory } from 'src/libs/test-utils/mocks/factories/team-factory.mock'; -import { UserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/userDto-factory'; +import { UserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/userDto-factory.mock'; import { NotFoundException } from '@nestjs/common'; import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface'; import { GetTokenAuthServiceInterface } from 'src/modules/auth/interfaces/services/get-token.auth.service.interface'; 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 9182b71e0..b741e918c 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -1,82 +1,62 @@ -import { DeleteBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/delete.board.user.service.interface'; -import { UpdateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/update.board.user.service.interface'; -import { GetBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/get.board.user.service.interface'; -import { CreateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/create.board.user.service.interface'; +import { boardUserRepository, updateBoardService } from './../boards.providers'; import { Test, TestingModule } from '@nestjs/testing'; -import { ConfigService } from '@nestjs/config'; +import { getTeamService } from 'src/modules/teams/providers'; +import { boardRepository } from '../boards.providers'; import SocketGateway from 'src/modules/socket/gateway/socket.gateway'; -import { EventEmitter2 } from '@nestjs/event-emitter'; +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { UpdateBoardServiceInterface } from '../interfaces/services/update.board.service.interface'; +import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface'; +import { BoardRepositoryInterface } from '../repositories/board.repository.interface'; +import { BoardUserRepositoryInterface } from '../repositories/board-user.repository.interface'; +import { DeleteCardServiceInterface } from 'src/modules/cards/interfaces/services/delete.card.service.interface'; +import { deleteCardService } from 'src/modules/cards/cards.providers'; import * as CommunicationsType from 'src/modules/communication/interfaces/types'; -import * as Cards from 'src/modules/cards/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 { DeepMocked, createMock } from '@golevelup/ts-jest'; -import { BoardPhases } from 'src/libs/enum/board.phases'; -import { BadRequestException } from '@nestjs/common'; -import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory.mock'; -import { SLACK_ENABLE, SLACK_MASTER_CHANNEL_ID } from 'src/libs/constants/slack'; -import { FRONTEND_URL } from 'src/libs/constants/frontend'; import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface'; import { SendMessageServiceInterface } from 'src/modules/communication/interfaces/send-message.service.interface'; -import { DeleteCardServiceInterface } from 'src/modules/cards/interfaces/services/delete.card.service.interface'; -import { BoardRepositoryInterface } from '../repositories/board.repository.interface'; -import { updateBoardService } from '../boards.providers'; -import { UpdateBoardServiceInterface } from '../interfaces/services/update.board.service.interface'; -import { TeamFactory } from 'src/libs/test-utils/mocks/factories/team-factory.mock'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ConfigService } from '@nestjs/config'; +import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory.mock'; +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'; -describe('UpdateBoardService', () => { - let service: UpdateBoardServiceInterface; - let eventEmitterMock: DeepMocked; +describe('GetUpdateBoardService', () => { + let boardService: UpdateBoardServiceInterface; let boardRepositoryMock: DeepMocked; - let configServiceMock: DeepMocked; - let slackSendMessageServiceMock: DeepMocked; - - const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; + let boardUserRepositoryMock: DeepMocked; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ updateBoardService, { - provide: Teams.TYPES.services.GetTeamService, - useValue: {} - }, - { - provide: CommunicationsType.TYPES.services.SlackCommunicationService, - useValue: createMock() + provide: getTeamService.provide, + useValue: createMock() }, { provide: CommunicationsType.TYPES.services.SlackSendMessageService, useValue: createMock() }, { - provide: SocketGateway, - useValue: createMock() + provide: CommunicationsType.TYPES.services.SlackCommunicationService, + useValue: createMock() }, { - provide: Cards.TYPES.services.DeleteCardService, + provide: deleteCardService.provide, useValue: createMock() }, { - provide: Boards.TYPES.repositories.BoardRepository, + provide: boardRepository.provide, useValue: createMock() }, { - provide: BoardUsers.TYPES.services.CreateBoardUserService, - useValue: createMock() - }, - { - provide: BoardUsers.TYPES.services.GetBoardUserService, - useValue: createMock() - }, - { - provide: BoardUsers.TYPES.services.UpdateBoardUserService, - useValue: createMock() + provide: boardUserRepository.provide, + useValue: createMock() }, { - provide: BoardUsers.TYPES.services.DeleteBoardUserService, - useValue: createMock() + provide: SocketGateway, + useValue: createMock() }, { provide: EventEmitter2, @@ -88,81 +68,58 @@ describe('UpdateBoardService', () => { } ] }).compile(); - service = module.get(updateBoardService.provide); - eventEmitterMock = module.get(EventEmitter2); + + boardService = module.get(updateBoardService.provide); boardRepositoryMock = module.get(Boards.TYPES.repositories.BoardRepository); - configServiceMock = module.get(ConfigService); - slackSendMessageServiceMock = module.get( - CommunicationsType.TYPES.services.SlackSendMessageService - ); + boardUserRepositoryMock = module.get(Boards.TYPES.repositories.BoardUserRepository); }); beforeEach(() => { + jest.restoreAllMocks(); jest.clearAllMocks(); }); it('should be defined', () => { - expect(service).toBeDefined(); + expect(boardService).toBeDefined(); }); - describe('updatePhase', () => { + describe('update', () => { it('should be defined', () => { - expect(service.updatePhase).toBeDefined(); + expect(boardService.update).toBeDefined(); }); - it('should call boardRepository ', async () => { - await service.updatePhase(boardPhaseDto); - expect(boardRepositoryMock.updatePhase).toBeCalledTimes(1); - }); + it('should call boardRepository', async () => { + const board = BoardFactory.create(); + const boardDto = UpdateBoardDtoFactory.create(); + const boardUser = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - it('should throw badRequestException when boardRepository fails', async () => { - // Set up the board repository mock to reject with an error - boardRepositoryMock.updatePhase.mockRejectedValueOnce(new Error('Some error')); + boardUserRepositoryMock.getVotesCount.mockResolvedValueOnce(boardUser); - // Verify that the service method being tested throws a BadRequestException - expect(async () => await service.updatePhase(boardPhaseDto)).rejects.toThrowError( - BadRequestException - ); + await boardService.update(board._id, boardDto); + + expect(boardRepositoryMock.getBoard).toBeCalledTimes(1); }); - it('should call websocket with eventEmitter', async () => { - // Call the service method being tested - await service.updatePhase(boardPhaseDto); + it('should throw error if board not found', async () => { + const boardDto = UpdateBoardDtoFactory.create(); - // Verify that the eventEmitterMock.emit method was called exactly once - expect(eventEmitterMock.emit).toHaveBeenCalledTimes(1); + boardRepositoryMock.getBoard.mockResolvedValue(null); + expect(async () => await boardService.update('-1', boardDto)).rejects.toThrow( + NotFoundException + ); }); - it('should call slackSendMessageService.execute once with slackMessageDto', async () => { - // Create a fake board object with the specified properties + it('should call getBoardResponsibleInfo', async () => { const board = BoardFactory.create(); - board.team = TeamFactory.create({ name: 'xgeeks' }); - - board.phase = BoardPhases.SUBMITTED; - board.slackEnable = true; - - const table = { - [SLACK_MASTER_CHANNEL_ID]: '6405f9a04633b1668f71c068', - [SLACK_ENABLE]: true, - [FRONTEND_URL]: 'https://split.kigroup.de/' - }; - - // Set up the board repository mock to resolve with the fake board object - boardRepositoryMock.updatePhase.mockResolvedValue(board); + const boardDto = UpdateBoardDtoFactory.create(); + const boardUser = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - // Set up the configuration service mock - configServiceMock.getOrThrow.mockImplementation((key: string) => { - return table[key]; - }); + boardRepositoryMock.getBoard.mockResolvedValueOnce(board); + boardUserRepositoryMock.getVotesCount.mockResolvedValueOnce(boardUser); - // Call the service method being tested - await service.updatePhase(boardPhaseDto); + await boardService.update(board._id, boardDto); - // Verify that the slackSendMessageService.execute method with correct data 1 time - expect(slackSendMessageServiceMock.execute).toHaveBeenNthCalledWith(1, { - slackChannelId: '6405f9a04633b1668f71c068', - message: expect.stringContaining('https://split.kigroup.de/') - }); + expect(boardUserRepositoryMock.getBoardResponsible).toBeCalledTimes(1); }); }); }); From ba24fe4addbc3eea1be7c67df3154d1c39bcda7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Wed, 15 Mar 2023 11:14:43 +0000 Subject: [PATCH 04/14] feat: add boardUser factory --- .../dto/boardUserDto-factory.mock.ts | 19 +++++++ .../factories/dto/cardDto-factory.mock.ts | 10 ++-- .../factories/dto/cardItemDto-factory.mock.ts | 6 +- .../factories/dto/columnDto-factory.mock.ts | 6 +- .../factories/dto/commentsDto-factory.mock.ts | 2 +- .../services/update.board.service.spec.ts | 57 ++++++++++++++++++- .../boards/services/update.board.service.ts | 2 +- 7 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts diff --git a/backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts new file mode 100644 index 000000000..5bdbd780a --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts @@ -0,0 +1,19 @@ +import faker from '@faker-js/faker'; +import { BoardRoles } from 'src/libs/enum/board.roles'; +import BoardUserDto from 'src/modules/boards/dto/board.user.dto'; +import { buildTestFactory } from '../generic-factory.mock'; + +const mockBoardUserDto = () => { + return { + id: faker.datatype.uuid(), + role: faker.helpers.arrayElement([BoardRoles.MEMBER, BoardRoles.RESPONSIBLE]), + user: faker.datatype.uuid(), + board: faker.datatype.uuid(), + votesCount: Math.random() * 10, + isNewJoiner: faker.datatype.boolean() + }; +}; + +export const BoardUserDtoFactory = buildTestFactory(() => { + return mockBoardUserDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts index ab525a890..83e63dd9d 100644 --- a/backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/dto/cardDto-factory.mock.ts @@ -1,21 +1,21 @@ import faker from '@faker-js/faker'; import CardDto from 'src/modules/cards/dto/card.dto'; import { buildTestFactory } from '../generic-factory.mock'; -import { cardItemDtoFactory } from './cardItemDto-factory.mock'; -import { commentDtoFactory } from './commentsDto-factory.mock'; +import { CardItemDtoFactory } from './cardItemDto-factory.mock'; +import { CommentDtoFactory } from './commentsDto-factory.mock'; const mockCardDto = () => { return { - items: [cardItemDtoFactory.create()], + items: [CardItemDtoFactory.create()], id: faker.database.mongodbObjectId(), text: faker.lorem.words(), createdBy: faker.datatype.uuid(), - comments: [commentDtoFactory.create()], + comments: [CommentDtoFactory.create()], votes: [], anonymous: faker.datatype.boolean() }; }; -export const cardDtoFactory = buildTestFactory(() => { +export const CardDtoFactory = buildTestFactory(() => { return mockCardDto(); }); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts index b0feebb66..5864aa9f7 100644 --- a/backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/dto/cardItemDto-factory.mock.ts @@ -1,19 +1,19 @@ import faker from '@faker-js/faker'; import CardItemDto from 'src/modules/cards/dto/card.item.dto'; import { buildTestFactory } from '../generic-factory.mock'; -import { commentDtoFactory } from './commentsDto-factory.mock'; +import { CommentDtoFactory } from './commentsDto-factory.mock'; const mockCardItemDto = () => { return { id: faker.database.mongodbObjectId(), text: faker.lorem.words(), createdBy: faker.datatype.uuid(), - comments: [commentDtoFactory.create()], + comments: [CommentDtoFactory.create()], votes: [], anonymous: faker.datatype.boolean() }; }; -export const cardItemDtoFactory = buildTestFactory(() => { +export const CardItemDtoFactory = buildTestFactory(() => { return mockCardItemDto(); }); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts index 11e9edd23..8be2803b0 100644 --- a/backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/dto/columnDto-factory.mock.ts @@ -1,7 +1,7 @@ import faker from '@faker-js/faker'; import ColumnDto from 'src/modules/columns/dto/column.dto'; import { buildTestFactory } from '../generic-factory.mock'; -import { cardDtoFactory } from './cardDto-factory.mock'; +import { CardDtoFactory } from './cardDto-factory.mock'; const mockColumnDto = () => { return { @@ -15,12 +15,12 @@ const mockColumnDto = () => { '#9DCAFF', '#FEB9A9' ]), - cards: [cardDtoFactory.create()], + cards: [CardDtoFactory.create()], cardText: faker.lorem.words(), isDefaultText: faker.datatype.boolean() }; }; -export const columnDtoFactory = buildTestFactory(() => { +export const ColumnDtoFactory = buildTestFactory(() => { return mockColumnDto(); }); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts index f6ed00c1c..112a597cf 100644 --- a/backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/dto/commentsDto-factory.mock.ts @@ -10,6 +10,6 @@ const mockCommentDto = () => { }; }; -export const commentDtoFactory = buildTestFactory(() => { +export const CommentDtoFactory = buildTestFactory(() => { return mockCommentDto(); }); 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 b741e918c..9f96b640c 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -1,4 +1,4 @@ -import { boardUserRepository, updateBoardService } from './../boards.providers'; +import { updateBoardService } from './../boards.providers'; import { Test, TestingModule } from '@nestjs/testing'; import { getTeamService } from 'src/modules/teams/providers'; import { boardRepository } from '../boards.providers'; @@ -7,11 +7,11 @@ import { DeepMocked, createMock } from '@golevelup/ts-jest'; import { UpdateBoardServiceInterface } from '../interfaces/services/update.board.service.interface'; import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface'; import { BoardRepositoryInterface } from '../repositories/board.repository.interface'; -import { BoardUserRepositoryInterface } from '../repositories/board-user.repository.interface'; import { DeleteCardServiceInterface } from 'src/modules/cards/interfaces/services/delete.card.service.interface'; import { deleteCardService } from 'src/modules/cards/cards.providers'; 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 { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface'; import { SendMessageServiceInterface } from 'src/modules/communication/interfaces/send-message.service.interface'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -20,11 +20,17 @@ 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 } from 'src/modules/boardusers/boardusers.providers'; +import { BoardRoles } from 'src/libs/enum/board.roles'; +import { BoardUserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock'; +import { UpdateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/update.board.user.service.interface'; describe('GetUpdateBoardService', () => { let boardService: UpdateBoardServiceInterface; let boardRepositoryMock: DeepMocked; let boardUserRepositoryMock: DeepMocked; + let boardUserUpdateServiceMock: DeepMocked; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -71,7 +77,8 @@ describe('GetUpdateBoardService', () => { boardService = module.get(updateBoardService.provide); boardRepositoryMock = module.get(Boards.TYPES.repositories.BoardRepository); - boardUserRepositoryMock = module.get(Boards.TYPES.repositories.BoardUserRepository); + boardUserRepositoryMock = module.get(BoardUsers.TYPES.repositories.BoardUserRepository); + boardUserUpdateServiceMock = module.get(BoardUsers.TYPES.services.UpdateBoardUserService); }); beforeEach(() => { @@ -121,5 +128,49 @@ describe('GetUpdateBoardService', () => { expect(boardUserRepositoryMock.getBoardResponsible).toBeCalledTimes(1); }); + + it('should return undefined if there is not a responsible when calling getBoardResponsibleInfo ', async () => { + const board = BoardFactory.create({ isSubBoard: true }); + //const mainBoard = BoardFactory.create(); + const currentResponsible = BoardUserFactory.create({ + role: BoardRoles.RESPONSIBLE, + board: board._id + }); + const boardUsersDto = BoardUserDtoFactory.createMany(3, [ + { board: board._id, role: BoardRoles.RESPONSIBLE }, + { + board: board._id, + role: BoardRoles.MEMBER, + user: String(currentResponsible.user), + _id: String(currentResponsible._id) + }, + { board: board._id, role: BoardRoles.MEMBER } + ]); + const newResponsible = BoardUserFactory.create({ + board: board._id, + role: BoardRoles.RESPONSIBLE, + user: String(boardUsersDto[0].user), + _id: String(boardUsersDto[0]._id) + }); + // const updateBoardDto = UpdateBoardDtoFactory.create({ + // responsible: newResponsible, + // mainBoardId: mainBoard._id, + // users: boardUsersDto, + // maxVotes: undefined + // }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(board); + + //gets current responsible from the board + boardUserRepositoryMock.getBoardResponsible.mockResolvedValueOnce(currentResponsible); + + jest + .spyOn(boardUserUpdateServiceMock, 'updateBoardUserRole') + .mockResolvedValueOnce(newResponsible); + + // await boardService.update(board._id, boardDto); + + // expect(boardUserRepositoryMock.getBoardResponsible).toBeCalledTimes(1); + }); }); }); diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index 70be3943f..ccda20fb0 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -337,7 +337,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { .map((boardUser) => { const typedBoardUser = boardUser.user as unknown as User; - return this.boardUserRepository.updateBoardUserRole( + return this.updateBoardUserService.updateBoardUserRole( boardId, typedBoardUser._id, boardUser.role From 78ae42a705c2acd0723e937ddd4554b0b377bab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Wed, 15 Mar 2023 12:31:13 +0000 Subject: [PATCH 05/14] feat: change logic of update function --- .../factories/dto/boardDto-factory.mock.ts | 4 +- .../services/update.board.service.spec.ts | 164 +++++++++++------- .../boards/services/update.board.service.ts | 105 ++++++----- 3 files changed, 170 insertions(+), 103 deletions(-) diff --git a/backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts index 6c09886da..6194142d2 100644 --- a/backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/dto/boardDto-factory.mock.ts @@ -2,13 +2,13 @@ import faker from '@faker-js/faker'; import { BoardPhases } from 'src/libs/enum/board.phases'; import BoardDto from 'src/modules/boards/dto/board.dto'; import { buildTestFactory } from '../generic-factory.mock'; -import { columnDtoFactory } from './columnDto-factory.mock'; +import { ColumnDtoFactory } from './columnDto-factory.mock'; const mockBoardDto = () => { return { _id: faker.database.mongodbObjectId(), title: faker.lorem.words(), - columns: columnDtoFactory.createMany(3), + columns: ColumnDtoFactory.createMany(3), isPublic: faker.datatype.boolean(), maxVotes: faker.datatype.number({ min: 0, max: 6 }), maxUsers: 0, 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 9f96b640c..5c40368a1 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -19,18 +19,26 @@ import { ConfigService } from '@nestjs/config'; import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory.mock'; 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 { BadRequestException, NotFoundException } from '@nestjs/common'; import { BoardUserRepositoryInterface } from 'src/modules/boardusers/interfaces/repositories/board-user.repository.interface'; -import { boardUserRepository } from 'src/modules/boardusers/boardusers.providers'; -import { BoardRoles } from 'src/libs/enum/board.roles'; -import { BoardUserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock'; +import { + boardUserRepository, + createBoardUserService, + deleteBoardUserService, + getBoardUserService, + updateBoardUserService +} from 'src/modules/boardusers/boardusers.providers'; import { UpdateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/update.board.user.service.interface'; +import { GetBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/get.board.user.service.interface'; +import { CreateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/create.board.user.service.interface'; +import { DeleteBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/delete.board.user.service.interface'; describe('GetUpdateBoardService', () => { let boardService: UpdateBoardServiceInterface; let boardRepositoryMock: DeepMocked; - let boardUserRepositoryMock: DeepMocked; - let boardUserUpdateServiceMock: DeepMocked; + // let boardUserRepositoryMock: DeepMocked; + // let boardUserUpdateServiceMock: DeepMocked; + let getBoardUserServiceMock: DeepMocked; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -52,6 +60,22 @@ describe('GetUpdateBoardService', () => { provide: deleteCardService.provide, useValue: createMock() }, + { + provide: getBoardUserService.provide, + useValue: createMock() + }, + { + provide: createBoardUserService.provide, + useValue: createMock() + }, + { + provide: updateBoardUserService.provide, + useValue: createMock() + }, + { + provide: deleteBoardUserService.provide, + useValue: createMock() + }, { provide: boardRepository.provide, useValue: createMock() @@ -77,8 +101,9 @@ describe('GetUpdateBoardService', () => { boardService = module.get(updateBoardService.provide); boardRepositoryMock = module.get(Boards.TYPES.repositories.BoardRepository); - boardUserRepositoryMock = module.get(BoardUsers.TYPES.repositories.BoardUserRepository); - boardUserUpdateServiceMock = module.get(BoardUsers.TYPES.services.UpdateBoardUserService); + // boardUserRepositoryMock = module.get(BoardUsers.TYPES.repositories.BoardUserRepository); + // boardUserUpdateServiceMock = module.get(BoardUsers.TYPES.services.UpdateBoardUserService); + getBoardUserServiceMock = module.get(BoardUsers.TYPES.services.GetBoardUserService); }); beforeEach(() => { @@ -95,82 +120,97 @@ describe('GetUpdateBoardService', () => { expect(boardService.update).toBeDefined(); }); + it('should throw error if max votes is less than the highest votes on board', async () => { + const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: 2 }); + const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 3 }, { votesCount: 1 }]); + + jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); + + expect(async () => await boardService.update('1', updateBoardDto)).rejects.toThrow( + BadRequestException + ); + }); + it('should call boardRepository', async () => { const board = BoardFactory.create(); - const boardDto = UpdateBoardDtoFactory.create(); - const boardUser = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); + const updateBoardDto = UpdateBoardDtoFactory.create(); + const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - boardUserRepositoryMock.getVotesCount.mockResolvedValueOnce(boardUser); + jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); - await boardService.update(board._id, boardDto); + await boardService.update(board._id, updateBoardDto); expect(boardRepositoryMock.getBoard).toBeCalledTimes(1); }); it('should throw error if board not found', async () => { - const boardDto = UpdateBoardDtoFactory.create(); + const updateBoardDto = UpdateBoardDtoFactory.create(); + const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); + + jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); boardRepositoryMock.getBoard.mockResolvedValue(null); - expect(async () => await boardService.update('-1', boardDto)).rejects.toThrow( + expect(async () => await boardService.update('-1', updateBoardDto)).rejects.toThrow( NotFoundException ); }); it('should call getBoardResponsibleInfo', async () => { const board = BoardFactory.create(); - const boardDto = UpdateBoardDtoFactory.create(); - const boardUser = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - - boardRepositoryMock.getBoard.mockResolvedValueOnce(board); - boardUserRepositoryMock.getVotesCount.mockResolvedValueOnce(boardUser); - - await boardService.update(board._id, boardDto); - - expect(boardUserRepositoryMock.getBoardResponsible).toBeCalledTimes(1); - }); + const updateBoardDto = UpdateBoardDtoFactory.create(); + const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - it('should return undefined if there is not a responsible when calling getBoardResponsibleInfo ', async () => { - const board = BoardFactory.create({ isSubBoard: true }); - //const mainBoard = BoardFactory.create(); - const currentResponsible = BoardUserFactory.create({ - role: BoardRoles.RESPONSIBLE, - board: board._id - }); - const boardUsersDto = BoardUserDtoFactory.createMany(3, [ - { board: board._id, role: BoardRoles.RESPONSIBLE }, - { - board: board._id, - role: BoardRoles.MEMBER, - user: String(currentResponsible.user), - _id: String(currentResponsible._id) - }, - { board: board._id, role: BoardRoles.MEMBER } - ]); - const newResponsible = BoardUserFactory.create({ - board: board._id, - role: BoardRoles.RESPONSIBLE, - user: String(boardUsersDto[0].user), - _id: String(boardUsersDto[0]._id) - }); - // const updateBoardDto = UpdateBoardDtoFactory.create({ - // responsible: newResponsible, - // mainBoardId: mainBoard._id, - // users: boardUsersDto, - // maxVotes: undefined - // }); + jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); boardRepositoryMock.getBoard.mockResolvedValueOnce(board); - //gets current responsible from the board - boardUserRepositoryMock.getBoardResponsible.mockResolvedValueOnce(currentResponsible); + await boardService.update(board._id, updateBoardDto); - jest - .spyOn(boardUserUpdateServiceMock, 'updateBoardUserRole') - .mockResolvedValueOnce(newResponsible); - - // await boardService.update(board._id, boardDto); - - // expect(boardUserRepositoryMock.getBoardResponsible).toBeCalledTimes(1); + expect(getBoardUserServiceMock.getBoardResponsible).toBeCalledTimes(1); }); + + // it('should return undefined if there is not a responsible when calling getBoardResponsibleInfo ', async () => { + // const board = BoardFactory.create({ isSubBoard: true }); + // //const mainBoard = BoardFactory.create(); + // const currentResponsible = BoardUserFactory.create({ + // role: BoardRoles.RESPONSIBLE, + // board: board._id + // }); + // const boardUsersDto = BoardUserDtoFactory.createMany(3, [ + // { board: board._id, role: BoardRoles.RESPONSIBLE }, + // { + // board: board._id, + // role: BoardRoles.MEMBER, + // user: String(currentResponsible.user), + // _id: String(currentResponsible._id) + // }, + // { board: board._id, role: BoardRoles.MEMBER } + // ]); + // const newResponsible = BoardUserFactory.create({ + // board: board._id, + // role: BoardRoles.RESPONSIBLE, + // user: String(boardUsersDto[0].user), + // _id: String(boardUsersDto[0]._id) + // }); + // // const updateBoardDto = UpdateBoardDtoFactory.create({ + // // responsible: newResponsible, + // // mainBoardId: mainBoard._id, + // // users: boardUsersDto, + // // maxVotes: undefined + // // }); + + // boardRepositoryMock.getBoard.mockResolvedValueOnce(board); + + // //gets current responsible from the board + // boardUserRepositoryMock.getBoardResponsible.mockResolvedValueOnce(currentResponsible); + + // jest + // .spyOn(boardUserUpdateServiceMock, 'updateBoardUserRole') + // .mockResolvedValueOnce(newResponsible); + + // // await boardService.update(board._id, boardDto); + + // // expect(boardUserRepositoryMock.getBoardResponsible).toBeCalledTimes(1); + // }); }); }); diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index ccda20fb0..6b176dfde 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -72,6 +72,23 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { ) {} async update(boardId: string, boardData: UpdateBoardDto) { + /** + * Only can change the maxVotes if: + * - new maxVotes not empty + * - current highest votes equals to zero + * - or current highest votes lower than new maxVotes + */ + + if (!isEmpty(boardData.maxVotes)) { + const highestVotes = await this.getHighestVotesOnBoard(boardId); + + if (highestVotes > Number(boardData.maxVotes)) { + throw new BadRequestException( + `You can't set a lower value to max votes. Please insert a value higher or equals than ${highestVotes}!` + ); + } + } + const board = await this.boardRepository.getBoard(boardId); if (!board) { @@ -94,23 +111,31 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { * - and the current responsible isn't the new responsible */ if (boardData.users && String(currentResponsible?.id) !== String(newResponsible?.id)) { - if (isSubBoard) { - this.updateBoardUsersRole( - boardId, - boardData.users, - String(currentResponsible.id), - String(newResponsible.id) - ); - } - - const mainBoardId = boardData.mainBoardId; - - this.updateBoardUsersRole( - mainBoardId, + this.changeResponsibleOnBoard( + isSubBoard, + boardId, + boardData.mainBoardId, boardData.users, String(currentResponsible.id), String(newResponsible.id) ); + // if (isSubBoard) { + // this.updateBoardUsersRole( + // boardId, + // boardData.users, + // String(currentResponsible.id), + // String(newResponsible.id) + // ); + // } + + // const mainBoardId = boardData.mainBoardId; + + // this.updateBoardUsersRole( + // mainBoardId, + // boardData.users, + // String(currentResponsible.id), + // String(newResponsible.id) + // ); } /** @@ -134,23 +159,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { board.columns = await this.updateRegularBoard(boardId, boardData, board); } - /** - * Only can change the maxVotes if: - * - new maxVotes not empty - * - current highest votes equals to zero - * - or current highest votes lower than new maxVotes - */ - - if (!isEmpty(boardData.maxVotes)) { - const highestVotes = await this.getHighestVotesOnBoard(boardId); - - if (highestVotes > Number(boardData.maxVotes)) { - throw new BadRequestException( - `You can't set a lower value to max votes. Please insert a value higher or equals than ${highestVotes}!` - ); - } - } - const updatedBoard = await this.boardRepository.updateBoard(boardId, board, true); if (!updatedBoard) throw new BadRequestException(UPDATE_FAILED); @@ -295,6 +303,20 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { /* --------------- HELPERS --------------- */ + /** + * Method to get the highest value of votesCount on Board Users + * @param boardId String + * @return number + */ + private async getHighestVotesOnBoard(boardId: string): Promise { + const votesCount = await this.getBoardUserService.getVotesCount(boardId); + + return votesCount.reduce( + (prev, current) => (current.votesCount > prev ? current.votesCount : prev), + 0 + ); + } + /** * Method to get current responsible to a specific board * @param boardId String @@ -347,17 +369,22 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { } /** - * Method to get the highest value of votesCount on Board Users - * @param boardId String - * @return number + * Method to change board responsible + * @return void */ - private async getHighestVotesOnBoard(boardId: string): Promise { - const votesCount = await this.getBoardUserService.getVotesCount(boardId); + private changeResponsibleOnBoard( + isSubBoard: boolean, + boardId: string, + mainBoardId: string, + users: BoardUserDto[], + currentResponsibleId: string, + newResponsibleId: string + ) { + if (isSubBoard) { + this.updateBoardUsersRole(boardId, users, currentResponsibleId, newResponsibleId); + } - return votesCount.reduce( - (prev, current) => (current.votesCount > prev ? current.votesCount : prev), - 0 - ); + this.updateBoardUsersRole(mainBoardId, users, currentResponsibleId, newResponsibleId); } private async updateRegularBoard(boardId: string, boardData: UpdateBoardDto, board: Board) { From 4d0c7a00a5eeb7ac684f47406b7c58c0d790b6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Wed, 15 Mar 2023 18:45:43 +0000 Subject: [PATCH 06/14] test: update function --- .../dto/boardUserDto-factory.mock.ts | 3 +- backend/src/modules/boards/dto/board.dto.ts | 5 +- .../src/modules/boards/dto/board.user.dto.ts | 5 +- .../services/update.board.service.spec.ts | 250 +++++++++++++----- .../boards/services/update.board.service.ts | 24 +- 5 files changed, 201 insertions(+), 86 deletions(-) diff --git a/backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts index 5bdbd780a..530da0621 100644 --- a/backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock.ts @@ -2,12 +2,13 @@ import faker from '@faker-js/faker'; import { BoardRoles } from 'src/libs/enum/board.roles'; import BoardUserDto from 'src/modules/boards/dto/board.user.dto'; import { buildTestFactory } from '../generic-factory.mock'; +import { UserFactory } from '../user-factory'; const mockBoardUserDto = () => { return { id: faker.datatype.uuid(), role: faker.helpers.arrayElement([BoardRoles.MEMBER, BoardRoles.RESPONSIBLE]), - user: faker.datatype.uuid(), + user: UserFactory.create(), board: faker.datatype.uuid(), votesCount: Math.random() * 10, isNewJoiner: faker.datatype.boolean() diff --git a/backend/src/modules/boards/dto/board.dto.ts b/backend/src/modules/boards/dto/board.dto.ts index 782dfdeb5..92993ad8e 100644 --- a/backend/src/modules/boards/dto/board.dto.ts +++ b/backend/src/modules/boards/dto/board.dto.ts @@ -78,7 +78,6 @@ export default class BoardDto { @ApiProperty({ type: BoardDto, isArray: true }) @ValidateNested({ each: true }) @Type(() => BoardDto) - @IsOptional() dividedBoards!: BoardDto[] | string[]; @ApiPropertyOptional({ type: String }) @@ -92,22 +91,24 @@ export default class BoardDto { socketId?: string; @ApiPropertyOptional({ type: BoardUserDto, isArray: true }) - @IsOptional() @Validate(CheckUniqueUsers) users!: BoardUserDto[]; @ApiPropertyOptional({ default: true }) @IsNotEmpty() + @IsOptional() @IsBoolean() recurrent?: boolean; @ApiPropertyOptional({ default: false }) @IsNotEmpty() + @IsOptional() @IsBoolean() isSubBoard?: boolean; @ApiPropertyOptional({ default: 0 }) @IsNotEmpty() + @IsOptional() @IsNumber() boardNumber?: number; diff --git a/backend/src/modules/boards/dto/board.user.dto.ts b/backend/src/modules/boards/dto/board.user.dto.ts index 110ad47c4..3c318ff4d 100644 --- a/backend/src/modules/boards/dto/board.user.dto.ts +++ b/backend/src/modules/boards/dto/board.user.dto.ts @@ -9,6 +9,7 @@ import { IsString } from 'class-validator'; import { BoardRoles } from 'src/libs/enum/board.roles'; +import User from 'src/modules/users/entities/user.schema'; export default class BoardUserDto { @ApiPropertyOptional() @@ -23,10 +24,8 @@ export default class BoardUserDto { role!: string; @ApiProperty() - @IsMongoId() - @IsString() @IsNotEmpty() - user!: string; + user!: string | User; @ApiProperty() @IsMongoId() 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 5c40368a1..1fb01ddf9 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -11,6 +11,7 @@ import { DeleteCardServiceInterface } from 'src/modules/cards/interfaces/service import { deleteCardService } from 'src/modules/cards/cards.providers'; import * as CommunicationsType from 'src/modules/communication/interfaces/types'; import * as Boards from 'src/modules/boards/interfaces/types'; +import * as Cards from 'src/modules/cards/interfaces/types'; import * as BoardUsers from 'src/modules/boardUsers/interfaces/types'; import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface'; import { SendMessageServiceInterface } from 'src/modules/communication/interfaces/send-message.service.interface'; @@ -32,13 +33,25 @@ import { UpdateBoardUserServiceInterface } from 'src/modules/boardusers/interfac import { GetBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/get.board.user.service.interface'; import { CreateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/create.board.user.service.interface'; import { DeleteBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/delete.board.user.service.interface'; +import { BoardRoles } from 'src/libs/enum/board.roles'; +import { BoardUserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock'; +import { BoardPhases } from 'src/libs/enum/board.phases'; +import { TeamFactory } from 'src/libs/test-utils/mocks/factories/team-factory.mock'; +import { SLACK_ENABLE, SLACK_MASTER_CHANNEL_ID } from 'src/libs/constants/slack'; +import { FRONTEND_URL } from 'src/libs/constants/frontend'; +import { UserFactory } from 'src/libs/test-utils/mocks/factories/user-factory'; +import User from 'src/modules/users/entities/user.schema'; +import ColumnDto from 'src/modules/columns/dto/column.dto'; describe('GetUpdateBoardService', () => { let boardService: UpdateBoardServiceInterface; let boardRepositoryMock: DeepMocked; - // let boardUserRepositoryMock: DeepMocked; - // let boardUserUpdateServiceMock: DeepMocked; + let eventEmitterMock: DeepMocked; + let updateBoardUserServiceMock: DeepMocked; let getBoardUserServiceMock: DeepMocked; + let configServiceMock: DeepMocked; + let slackSendMessageServiceMock: DeepMocked; + let deleteCardServiceMock: DeepMocked; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -101,9 +114,14 @@ describe('GetUpdateBoardService', () => { boardService = module.get(updateBoardService.provide); boardRepositoryMock = module.get(Boards.TYPES.repositories.BoardRepository); - // boardUserRepositoryMock = module.get(BoardUsers.TYPES.repositories.BoardUserRepository); - // boardUserUpdateServiceMock = module.get(BoardUsers.TYPES.services.UpdateBoardUserService); + updateBoardUserServiceMock = module.get(BoardUsers.TYPES.services.UpdateBoardUserService); getBoardUserServiceMock = module.get(BoardUsers.TYPES.services.GetBoardUserService); + deleteCardServiceMock = module.get(Cards.TYPES.services.DeleteCardService); + eventEmitterMock = module.get(EventEmitter2); + configServiceMock = module.get(ConfigService); + slackSendMessageServiceMock = module.get( + CommunicationsType.TYPES.services.SlackSendMessageService + ); }); beforeEach(() => { @@ -133,10 +151,7 @@ describe('GetUpdateBoardService', () => { it('should call boardRepository', async () => { const board = BoardFactory.create(); - const updateBoardDto = UpdateBoardDtoFactory.create(); - const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - - jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); + const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); await boardService.update(board._id, updateBoardDto); @@ -144,10 +159,7 @@ describe('GetUpdateBoardService', () => { }); it('should throw error if board not found', async () => { - const updateBoardDto = UpdateBoardDtoFactory.create(); - const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - - jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); + const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); boardRepositoryMock.getBoard.mockResolvedValue(null); expect(async () => await boardService.update('-1', updateBoardDto)).rejects.toThrow( @@ -157,10 +169,7 @@ describe('GetUpdateBoardService', () => { it('should call getBoardResponsibleInfo', async () => { const board = BoardFactory.create(); - const updateBoardDto = UpdateBoardDtoFactory.create(); - const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 2 }, { votesCount: 1 }]); - - jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); + const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); boardRepositoryMock.getBoard.mockResolvedValueOnce(board); @@ -169,48 +178,171 @@ describe('GetUpdateBoardService', () => { expect(getBoardUserServiceMock.getBoardResponsible).toBeCalledTimes(1); }); - // it('should return undefined if there is not a responsible when calling getBoardResponsibleInfo ', async () => { - // const board = BoardFactory.create({ isSubBoard: true }); - // //const mainBoard = BoardFactory.create(); - // const currentResponsible = BoardUserFactory.create({ - // role: BoardRoles.RESPONSIBLE, - // board: board._id - // }); - // const boardUsersDto = BoardUserDtoFactory.createMany(3, [ - // { board: board._id, role: BoardRoles.RESPONSIBLE }, - // { - // board: board._id, - // role: BoardRoles.MEMBER, - // user: String(currentResponsible.user), - // _id: String(currentResponsible._id) - // }, - // { board: board._id, role: BoardRoles.MEMBER } - // ]); - // const newResponsible = BoardUserFactory.create({ - // board: board._id, - // role: BoardRoles.RESPONSIBLE, - // user: String(boardUsersDto[0].user), - // _id: String(boardUsersDto[0]._id) - // }); - // // const updateBoardDto = UpdateBoardDtoFactory.create({ - // // responsible: newResponsible, - // // mainBoardId: mainBoard._id, - // // users: boardUsersDto, - // // maxVotes: undefined - // // }); - - // boardRepositoryMock.getBoard.mockResolvedValueOnce(board); - - // //gets current responsible from the board - // boardUserRepositoryMock.getBoardResponsible.mockResolvedValueOnce(currentResponsible); - - // jest - // .spyOn(boardUserUpdateServiceMock, 'updateBoardUserRole') - // .mockResolvedValueOnce(newResponsible); - - // // await boardService.update(board._id, boardDto); - - // // expect(boardUserRepositoryMock.getBoardResponsible).toBeCalledTimes(1); - // }); + it('should call changeResponsibleOnBoard if current responsible is not equal to new responsible', async () => { + const board = BoardFactory.create({ isSubBoard: true }); + const mainBoard = BoardFactory.create(); + const currentResponsible = BoardUserFactory.create({ + role: BoardRoles.RESPONSIBLE, + board: board._id + }); + + const boardUsersDto = BoardUserDtoFactory.createMany(3, [ + { board: board._id, role: BoardRoles.RESPONSIBLE }, + { + board: board._id, + role: BoardRoles.MEMBER, + user: currentResponsible.user as User, + _id: String(currentResponsible._id) + }, + { board: board._id, role: BoardRoles.MEMBER } + ]); + const newResponsible = BoardUserFactory.create({ + board: board._id, + role: BoardRoles.RESPONSIBLE, + user: UserFactory.create({ _id: (boardUsersDto[0].user as User)._id }), + _id: String(boardUsersDto[0]._id) + }); + const updateBoardDto = UpdateBoardDtoFactory.create({ + responsible: newResponsible, + mainBoardId: mainBoard._id, + users: boardUsersDto, + maxVotes: null, + _id: board._id, + isSubBoard: true + }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(board); + + //gets current responsible from the board + jest + .spyOn(getBoardUserServiceMock, 'getBoardResponsible') + .mockResolvedValue(currentResponsible); + + await boardService.update(board._id, updateBoardDto); + //update changeResponsibleOnBoard + expect(updateBoardUserServiceMock.updateBoardUserRole).toBeCalled(); + }); + + it('should throw error when update fails', async () => { + const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); + const board = BoardFactory.create(); + + boardRepositoryMock.getBoard.mockResolvedValue(board); + boardRepositoryMock.updateBoard.mockResolvedValueOnce(null); + + expect(async () => await boardService.update('1', updateBoardDto)).rejects.toThrow( + BadRequestException + ); + }); + + it('should return updated board', async () => { + const board = BoardFactory.create(); + const updateBoardDto = UpdateBoardDtoFactory.create({ + maxVotes: null, + title: 'Mock 2.0', + _id: board._id + }); + const boardResult = { ...board, title: updateBoardDto.title }; + + boardRepositoryMock.getBoard.mockResolvedValue(board); + boardRepositoryMock.updateBoard.mockResolvedValue(boardResult); + + const result = await boardService.update(board._id, updateBoardDto); + + expect(result).toEqual(boardResult); + }); + + it('should updated board from a regular board', async () => { + const board = BoardFactory.create({ isSubBoard: false, dividedBoards: [] }); + + board.columns[1].title = 'Make things'; + board.columns[1].color = '#FEB9A9'; + + const updateBoardDto = UpdateBoardDtoFactory.create({ + maxVotes: null, + _id: board._id, + isSubBoard: false, + dividedBoards: [], + columns: board.columns as ColumnDto[], + deletedColumns: [board.columns[0]._id] + }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(board); + + deleteCardServiceMock.deleteCardVotesFromColumn.mockResolvedValue(null); + + const boardResult = { ...board, columns: board.columns.slice(1) }; + + boardRepositoryMock.updateBoard.mockResolvedValue(boardResult); + + const result = await boardService.update(board._id, updateBoardDto); + + expect(result).toEqual(boardResult); + }); + }); + + describe('updatePhase', () => { + it('should be defined', () => { + expect(boardService.updatePhase).toBeDefined(); + }); + + it('should call boardRepository ', async () => { + const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; + await boardService.updatePhase(boardPhaseDto); + expect(boardRepositoryMock.updatePhase).toBeCalledTimes(1); + }); + + it('should throw badRequestException when boardRepository fails', async () => { + const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; + // Set up the board repository mock to reject with an error + boardRepositoryMock.updatePhase.mockRejectedValueOnce(new Error('Some error')); + + // Verify that the service method being tested throws a BadRequestException + expect(async () => await boardService.updatePhase(boardPhaseDto)).rejects.toThrowError( + BadRequestException + ); + }); + + it('should call websocket with eventEmitter', async () => { + const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; + // Call the service method being tested + await boardService.updatePhase(boardPhaseDto); + + // Verify that the eventEmitterMock.emit method was called exactly once + expect(eventEmitterMock.emit).toHaveBeenCalledTimes(1); + }); + + it('should call slackSendMessageService.execute once with slackMessageDto', async () => { + const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; + // Create a fake board object with the specified properties + const board = BoardFactory.create(); + board.team = TeamFactory.create({ name: 'xgeeks' }); + + board.phase = BoardPhases.SUBMITTED; + board.slackEnable = true; + + const table = { + [SLACK_MASTER_CHANNEL_ID]: '6405f9a04633b1668f71c068', + [SLACK_ENABLE]: true, + [FRONTEND_URL]: 'https://split.kigroup.de/' + }; + + // Set up the board repository mock to resolve with the fake board object + boardRepositoryMock.updatePhase.mockResolvedValue(board); + + // Set up the configuration service mock + configServiceMock.getOrThrow.mockImplementation((key: string) => { + return table[key]; + }); + + // Call the service method being tested + await boardService.updatePhase(boardPhaseDto); + + // Verify that the slackSendMessageService.execute method with correct data 1 time + expect(slackSendMessageServiceMock.execute).toHaveBeenNthCalledWith(1, { + slackChannelId: '6405f9a04633b1668f71c068', + message: expect.stringContaining('https://split.kigroup.de/') + }); + }); }); }); diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index 6b176dfde..696140f6f 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -119,23 +119,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { String(currentResponsible.id), String(newResponsible.id) ); - // if (isSubBoard) { - // this.updateBoardUsersRole( - // boardId, - // boardData.users, - // String(currentResponsible.id), - // String(newResponsible.id) - // ); - // } - - // const mainBoardId = boardData.mainBoardId; - - // this.updateBoardUsersRole( - // mainBoardId, - // boardData.users, - // String(currentResponsible.id), - // String(newResponsible.id) - // ); } /** @@ -260,7 +243,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { } async updateBoardParticipantsRole(boardUserToUpdateRole: BoardUserDto) { - const user = boardUserToUpdateRole.user as unknown as User; + const user = boardUserToUpdateRole.user as User; const updatedBoardUsers = await this.updateBoardUserService.updateBoardUserRole( boardUserToUpdateRole.board, @@ -353,11 +336,11 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { const promises = boardUsers .filter((boardUser) => [getIdFromObjectId(currentResponsibleId), newResponsibleId].includes( - (boardUser.user as unknown as User)._id + (boardUser.user as User)._id ) ) .map((boardUser) => { - const typedBoardUser = boardUser.user as unknown as User; + const typedBoardUser = boardUser.user as User; return this.updateBoardUserService.updateBoardUserRole( boardId, @@ -405,7 +388,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { * Updates the columns * * */ - const columns = boardData.columns.flatMap((col: Column | ColumnDto) => { if (col._id) { const columnBoard = board.columns.find( From e2f62e4559d61df4db49fadab80f7c3ff65e2a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Thu, 16 Mar 2023 16:32:05 +0000 Subject: [PATCH 07/14] feat: add tests to mergeBoards --- .../update.board.application.interface.ts | 4 +- .../update.board.service.interface.ts | 4 +- .../services/update.board.service.spec.ts | 205 +++++++++++++++--- .../boards/services/update.board.service.ts | 102 ++------- .../boards/utils/generate-subcolumns.ts | 56 +++++ .../boards/utils/merge-cards-from-subboard.ts | 18 ++ 6 files changed, 278 insertions(+), 111 deletions(-) create mode 100644 backend/src/modules/boards/utils/generate-subcolumns.ts create mode 100644 backend/src/modules/boards/utils/merge-cards-from-subboard.ts diff --git a/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts b/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts index 024281a93..52b49490b 100644 --- a/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts +++ b/backend/src/modules/boards/interfaces/applications/update.board.application.interface.ts @@ -1,12 +1,12 @@ import { LeanDocument } from 'mongoose'; import { UpdateBoardDto } from '../../dto/update-board.dto'; -import { BoardDocument } from '../../entities/board.schema'; +import Board, { BoardDocument } from '../../entities/board.schema'; import BoardUser from '../../entities/board.user.schema'; import UpdateBoardUserDto from 'src/modules/boards/dto/update-board-user.dto'; import { BoardPhaseDto } from 'src/libs/dto/board-phase.dto'; export interface UpdateBoardApplicationInterface { - update(boardId: string, boardData: UpdateBoardDto): Promise | null>; + update(boardId: string, boardData: UpdateBoardDto): Promise; mergeBoards( subBoardId: string, diff --git a/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts b/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts index 238dcac30..6d59b44b3 100644 --- a/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts +++ b/backend/src/modules/boards/interfaces/services/update.board.service.interface.ts @@ -2,12 +2,12 @@ import BoardUserDto from 'src/modules/boards/dto/board.user.dto'; import { LeanDocument } from 'mongoose'; import { TeamDto } from 'src/modules/communication/dto/team.dto'; import { UpdateBoardDto } from '../../dto/update-board.dto'; -import { BoardDocument } from '../../entities/board.schema'; +import Board, { BoardDocument } from '../../entities/board.schema'; import BoardUser from '../../entities/board.user.schema'; import { BoardPhaseDto } from 'src/libs/dto/board-phase.dto'; export interface UpdateBoardServiceInterface { - update(boardId: string, boardData: UpdateBoardDto): Promise | null>; + update(boardId: string, boardData: UpdateBoardDto): Promise; mergeBoards( subBoardId: string, 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 1fb01ddf9..dbc342f17 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -33,26 +33,32 @@ import { UpdateBoardUserServiceInterface } from 'src/modules/boardusers/interfac import { GetBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/get.board.user.service.interface'; import { CreateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/create.board.user.service.interface'; import { DeleteBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/delete.board.user.service.interface'; -import { BoardRoles } from 'src/libs/enum/board.roles'; -import { BoardUserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock'; import { BoardPhases } from 'src/libs/enum/board.phases'; import { TeamFactory } from 'src/libs/test-utils/mocks/factories/team-factory.mock'; import { SLACK_ENABLE, SLACK_MASTER_CHANNEL_ID } from 'src/libs/constants/slack'; import { FRONTEND_URL } from 'src/libs/constants/frontend'; +import ColumnDto from 'src/modules/columns/dto/column.dto'; +import faker from '@faker-js/faker'; +import { BoardRoles } from 'src/libs/enum/board.roles'; +import { BoardUserDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/boardUserDto-factory.mock'; import { UserFactory } from 'src/libs/test-utils/mocks/factories/user-factory'; import User from 'src/modules/users/entities/user.schema'; -import ColumnDto from 'src/modules/columns/dto/column.dto'; +import { generateNewSubColumns } from '../utils/generate-subcolumns'; +import { mergeCardsFromSubBoardColumnsIntoMainBoard } from '../utils/merge-cards-from-subboard'; describe('GetUpdateBoardService', () => { let boardService: UpdateBoardServiceInterface; + let updateBoardUserServiceMock: DeepMocked; let boardRepositoryMock: DeepMocked; let eventEmitterMock: DeepMocked; - let updateBoardUserServiceMock: DeepMocked; let getBoardUserServiceMock: DeepMocked; let configServiceMock: DeepMocked; let slackSendMessageServiceMock: DeepMocked; + let slackCommunicationServiceMock: DeepMocked; let deleteCardServiceMock: DeepMocked; + let socketServiceMock: DeepMocked; + beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -122,6 +128,10 @@ describe('GetUpdateBoardService', () => { slackSendMessageServiceMock = module.get( CommunicationsType.TYPES.services.SlackSendMessageService ); + slackCommunicationServiceMock = module.get( + CommunicationsType.TYPES.services.SlackCommunicationService + ); + socketServiceMock = module.get(SocketGateway); }); beforeEach(() => { @@ -149,15 +159,6 @@ describe('GetUpdateBoardService', () => { ); }); - it('should call boardRepository', async () => { - const board = BoardFactory.create(); - const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); - - await boardService.update(board._id, updateBoardDto); - - expect(boardRepositoryMock.getBoard).toBeCalledTimes(1); - }); - it('should throw error if board not found', async () => { const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); @@ -167,17 +168,6 @@ describe('GetUpdateBoardService', () => { ); }); - it('should call getBoardResponsibleInfo', async () => { - const board = BoardFactory.create(); - const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); - - boardRepositoryMock.getBoard.mockResolvedValueOnce(board); - - await boardService.update(board._id, updateBoardDto); - - expect(getBoardUserServiceMock.getBoardResponsible).toBeCalledTimes(1); - }); - it('should call changeResponsibleOnBoard if current responsible is not equal to new responsible', async () => { const board = BoardFactory.create({ isSubBoard: true }); const mainBoard = BoardFactory.create(); @@ -185,7 +175,6 @@ describe('GetUpdateBoardService', () => { role: BoardRoles.RESPONSIBLE, board: board._id }); - const boardUsersDto = BoardUserDtoFactory.createMany(3, [ { board: board._id, role: BoardRoles.RESPONSIBLE }, { @@ -235,7 +224,74 @@ describe('GetUpdateBoardService', () => { ); }); - it('should return updated board', async () => { + it('should call socketService if socketId exists', async () => { + const board = BoardFactory.create(); + const updateBoardDto = UpdateBoardDtoFactory.create({ + maxVotes: null, + title: 'Mock 2.0', + _id: board._id, + socketId: faker.datatype.uuid() + }); + const boardResult = { ...board, title: updateBoardDto.title }; + + boardRepositoryMock.getBoard.mockResolvedValue(board); + boardRepositoryMock.updateBoard.mockResolvedValue(boardResult); + + await boardService.update(board._id, updateBoardDto); + + expect(socketServiceMock.sendUpdatedBoard).toBeCalledTimes(1); + }); + + it('should call handleResponsiblesSlackMessage if board has a newResponsible and slack enable', async () => { + const board = BoardFactory.create({ + isSubBoard: true, + slackEnable: true, + slackChannelId: faker.datatype.uuid() + }); + const mainBoard = BoardFactory.create(); + const currentResponsible = BoardUserFactory.create({ + role: BoardRoles.RESPONSIBLE, + board: board._id + }); + const boardUsersDto = BoardUserDtoFactory.createMany(3, [ + { board: board._id, role: BoardRoles.RESPONSIBLE }, + { + board: board._id, + role: BoardRoles.MEMBER, + user: currentResponsible.user as User, + _id: String(currentResponsible._id) + }, + { board: board._id, role: BoardRoles.MEMBER } + ]); + const newResponsible = BoardUserFactory.create({ + board: board._id, + role: BoardRoles.RESPONSIBLE, + user: UserFactory.create({ _id: (boardUsersDto[0].user as User)._id }), + _id: String(boardUsersDto[0]._id) + }); + const updateBoardDto = UpdateBoardDtoFactory.create({ + responsible: newResponsible, + mainBoardId: mainBoard._id, + users: boardUsersDto, + maxVotes: null, + _id: board._id, + isSubBoard: true + }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(board); + boardRepositoryMock.updateBoard.mockResolvedValueOnce(board); + + //gets current responsible from the board + jest + .spyOn(getBoardUserServiceMock, 'getBoardResponsible') + .mockResolvedValue(currentResponsible); + + await boardService.update(board._id, updateBoardDto); + + expect(slackCommunicationServiceMock.executeResponsibleChange).toBeCalledTimes(1); + }); + + it('should update board', async () => { const board = BoardFactory.create(); const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null, @@ -252,7 +308,7 @@ describe('GetUpdateBoardService', () => { expect(result).toEqual(boardResult); }); - it('should updated board from a regular board', async () => { + it('should update board from a regular board', async () => { const board = BoardFactory.create({ isSubBoard: false, dividedBoards: [] }); board.columns[1].title = 'Make things'; @@ -268,7 +324,6 @@ describe('GetUpdateBoardService', () => { }); boardRepositoryMock.getBoard.mockResolvedValueOnce(board); - deleteCardServiceMock.deleteCardVotesFromColumn.mockResolvedValue(null); const boardResult = { ...board, columns: board.columns.slice(1) }; @@ -281,6 +336,100 @@ describe('GetUpdateBoardService', () => { }); }); + describe('mergeBoards', () => { + it('should throw error when subBoard, board or subBoard.submittedByUser are undefined', async () => { + const userId = faker.datatype.uuid(); + const board = BoardFactory.create({ isSubBoard: false }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(null); + boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); + + expect(async () => await boardService.mergeBoards('-1', userId)).rejects.toThrowError( + BadRequestException + ); + }); + + it('should throw error if mergeBoards fails', async () => { + const userId = faker.datatype.uuid(); + const board = BoardFactory.create({ isSubBoard: false }); + const subBoard = BoardFactory.create({ isSubBoard: true }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoard); + boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); + boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(null); + + expect(async () => await boardService.mergeBoards(subBoard._id, userId)).rejects.toThrowError( + BadRequestException + ); + }); + + it('should call executeMergeBoardNotification if board has slackChannelId and slackEnable', async () => { + const userId = faker.datatype.uuid(); + const board = BoardFactory.create({ + isSubBoard: false, + slackEnable: true, + slackChannelId: faker.datatype.uuid() + }); + const subBoard = BoardFactory.create({ isSubBoard: true }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoard); + boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); + boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(subBoard); + boardRepositoryMock.updateMergedBoard.mockResolvedValueOnce(board); + + await boardService.mergeBoards(subBoard._id, userId); + + expect(slackCommunicationServiceMock.executeMergeBoardNotification).toBeCalledTimes(1); + }); + + it('should call socketService if there is a socketId', async () => { + const userId = faker.datatype.uuid(); + const board = BoardFactory.create({ + isSubBoard: false, + slackEnable: true, + slackChannelId: faker.datatype.uuid() + }); + const subBoard = BoardFactory.create({ isSubBoard: true }); + const socketId = faker.datatype.uuid(); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoard); + boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); + boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(subBoard); + boardRepositoryMock.updateMergedBoard.mockResolvedValueOnce(board); + + await boardService.mergeBoards(subBoard._id, userId, socketId); + + expect(socketServiceMock.sendUpdatedAllBoard).toBeCalledTimes(1); + }); + + it('should return merged board', async () => { + const userId = faker.datatype.uuid(); + const board = BoardFactory.create({ + isSubBoard: false + }); + const subBoard = BoardFactory.create({ isSubBoard: true }); + const newSubColumns = generateNewSubColumns(subBoard); + + boardRepositoryMock.getBoard.mockResolvedValue(subBoard); + boardRepositoryMock.getBoardByQuery.mockResolvedValue(board); + + //mocks update of subBoard that is being merged + const subBoardUpdated = { ...subBoard, submitedByUser: userId, submitedAt: new Date() }; + boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(subBoardUpdated); + + //mocks update to the mainBoard to merge cards from subBoard + const boardResult = { + ...board, + columns: mergeCardsFromSubBoardColumnsIntoMainBoard([...board.columns], newSubColumns) + }; + boardRepositoryMock.updateMergedBoard.mockResolvedValueOnce(boardResult); + + const result = await boardService.mergeBoards(subBoard._id, userId); + + expect(result).toEqual(boardResult); + }); + }); + describe('updatePhase', () => { it('should be defined', () => { expect(boardService.updatePhase).toBeDefined(); diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index 696140f6f..7b075a8f7 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -7,7 +7,6 @@ import { NotFoundException, forwardRef } from '@nestjs/common'; -import { ObjectId } from 'mongoose'; import { getIdFromObjectId } from 'src/libs/utils/getIdFromObjectId'; import isEmpty from 'src/libs/utils/isEmpty'; import { TeamDto } from 'src/modules/communication/dto/team.dto'; @@ -44,6 +43,8 @@ import Team from 'src/modules/teams/entities/teams.schema'; import { GetBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/get.board.user.service.interface'; import { CreateBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/create.board.user.service.interface'; import { DeleteBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/delete.board.user.service.interface'; +import { generateNewSubColumns } from '../utils/generate-subcolumns'; +import { mergeCardsFromSubBoardColumnsIntoMainBoard } from '../utils/merge-cards-from-subboard'; @Injectable() export default class UpdateBoardService implements UpdateBoardServiceInterface { @@ -180,26 +181,37 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { if (!subBoard || !board || subBoard.submitedByUser) throw new BadRequestException(UPDATE_FAILED); - const team = await this.getTeamService.getTeam((board.team as ObjectId).toString()); - - if (!team) throw new BadRequestException(UPDATE_FAILED); - const columnsWitMergedCards = this.getColumnsFromMainBoardWithMergedCards(subBoard, board); await this.boardRepository.startTransaction(); try { - await this.boardRepository.updateMergedSubBoard(subBoardId, userId, true); - const result = await this.boardRepository.updateMergedBoard( + const updatedMergedSubBoard = await this.boardRepository.updateMergedSubBoard( + subBoardId, + userId, + true + ); + + if (!updatedMergedSubBoard) { + throw new BadRequestException(UPDATE_FAILED); + } + + const mergedBoard = await this.boardRepository.updateMergedBoard( board._id, columnsWitMergedCards, true ); + if (!mergedBoard) { + throw new BadRequestException(UPDATE_FAILED); + } + if (board.slackChannelId && board.slackEnable) { this.slackCommunicationService.executeMergeBoardNotification({ responsiblesChannelId: board.slackChannelId, teamNumber: subBoard.boardNumber, - isLastSubBoard: await this.checkIfIsLastBoardToMerge(result.dividedBoards as Board[]), + isLastSubBoard: await this.checkIfIsLastBoardToMerge( + mergedBoard.dividedBoards as Board[] + ), boardId: subBoardId, mainBoardId: board._id }); @@ -212,7 +224,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { await this.boardRepository.commitTransaction(); await this.boardRepository.endSession(); - return result; + return mergedBoard; } catch (e) { await this.boardRepository.abortTransaction(); } finally { @@ -445,74 +457,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { return count === dividedBoards.length; } - /** - * Method to generate columns from sub-board - * @param subBoard: Board - * @return Column[] - */ - private generateNewSubColumns(subBoard: Board) { - return [...subBoard.columns].map((column) => { - const newColumn = { - title: column.title, - color: column.color, - cardText: column.cardText, - isDefaultText: column.isDefaultText, - cards: column.cards.map((card) => { - const newCard = { - text: card.text, - createdBy: card.createdBy, - votes: card.votes, - anonymous: card.anonymous, - createdByTeam: subBoard.title.replace('board', ''), - comments: card.comments.map((comment) => { - return { - text: comment.text, - createdBy: comment.createdBy, - anonymous: comment.anonymous - }; - }), - items: card.items.map((cardItem) => { - return { - text: cardItem.text, - votes: cardItem.votes, - createdByTeam: subBoard.title.replace('board ', ''), - createdBy: cardItem.createdBy, - anonymous: cardItem.anonymous, - comments: cardItem.comments.map((comment) => { - return { - text: comment.text, - createdBy: comment.createdBy, - anonymous: comment.anonymous - }; - }), - createdAt: card.createdAt - }; - }), - createdAt: card.createdAt - }; - - return newCard; - }) - }; - - return newColumn as Column; - }); - } - - /** - * Method to merge cards from sub-board into a main board - * @param columns: Column[] - * @param subColumns: Column[] - * @return Column[] - */ - private mergeCardsFromSubBoardColumnsIntoMainBoard(columns: Column[], subColumns: Column[]) { - for (let i = 0; i < columns.length; i++) { - columns[i].cards = [...columns[i].cards, ...subColumns[i].cards]; - } - - return columns; - } - /** * Method to return columns with cards merged cards a from sub-board * @param subBoard: Board @@ -520,9 +464,9 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { * @return Column[] */ private getColumnsFromMainBoardWithMergedCards(subBoard: Board, board: Board) { - const newSubColumns = this.generateNewSubColumns(subBoard); + const newSubColumns = generateNewSubColumns(subBoard); - return this.mergeCardsFromSubBoardColumnsIntoMainBoard([...board.columns], newSubColumns); + return mergeCardsFromSubBoardColumnsIntoMainBoard([...board.columns], newSubColumns); } private generateMessage(phase: string, boardId: string, date: string, columns): string { diff --git a/backend/src/modules/boards/utils/generate-subcolumns.ts b/backend/src/modules/boards/utils/generate-subcolumns.ts new file mode 100644 index 000000000..86b42eb60 --- /dev/null +++ b/backend/src/modules/boards/utils/generate-subcolumns.ts @@ -0,0 +1,56 @@ +import Column from 'src/modules/columns/entities/column.schema'; +import Board from '../entities/board.schema'; + +/** + * Method to generate columns from sub-board + * @param subBoard: Board + * @return Column[] + */ +export const generateNewSubColumns = (subBoard: Board) => { + return [...subBoard.columns].map((column) => { + const newColumn = { + title: column.title, + color: column.color, + cardText: column.cardText, + isDefaultText: column.isDefaultText, + cards: column.cards.map((card) => { + const newCard = { + text: card.text, + createdBy: card.createdBy, + votes: card.votes, + anonymous: card.anonymous, + createdByTeam: subBoard.title.replace('board', ''), + comments: card.comments.map((comment) => { + return { + text: comment.text, + createdBy: comment.createdBy, + anonymous: comment.anonymous + }; + }), + items: card.items.map((cardItem) => { + return { + text: cardItem.text, + votes: cardItem.votes, + createdByTeam: subBoard.title.replace('board ', ''), + createdBy: cardItem.createdBy, + anonymous: cardItem.anonymous, + comments: cardItem.comments.map((comment) => { + return { + text: comment.text, + createdBy: comment.createdBy, + anonymous: comment.anonymous + }; + }), + createdAt: card.createdAt + }; + }), + createdAt: card.createdAt + }; + + return newCard; + }) + }; + + return newColumn as Column; + }); +}; diff --git a/backend/src/modules/boards/utils/merge-cards-from-subboard.ts b/backend/src/modules/boards/utils/merge-cards-from-subboard.ts new file mode 100644 index 000000000..e7d8441d3 --- /dev/null +++ b/backend/src/modules/boards/utils/merge-cards-from-subboard.ts @@ -0,0 +1,18 @@ +import Column from 'src/modules/columns/entities/column.schema'; + +/** + * Method to merge cards from sub-board into a main board + * @param columns: Column[] + * @param subColumns: Column[] + * @return Column[] + */ +export const mergeCardsFromSubBoardColumnsIntoMainBoard = ( + columns: Column[], + subColumns: Column[] +) => { + for (let i = 0; i < columns.length; i++) { + columns[i].cards = [...columns[i].cards, ...subColumns[i].cards]; + } + + return columns; +}; From f410e7907f7024d9e82559a698dd01e1f9d1a8d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Thu, 16 Mar 2023 18:29:36 +0000 Subject: [PATCH 08/14] test: add updateChannelId, updateBoardParticipants and updateBoardParticipanstRole --- .../mocks/factories/dto/teamDto-factory.ts | 22 +++ .../dto/userCommunicationDto-factory.mock.ts | 19 +++ .../services/update.board.service.spec.ts | 151 +++++++++++++++++- .../boards/services/update.board.service.ts | 18 +-- .../slack-communication.application.ts | 6 +- .../src/modules/communication/dto/team.dto.ts | 7 +- 6 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/teamDto-factory.ts create mode 100644 backend/src/libs/test-utils/mocks/factories/dto/userCommunicationDto-factory.mock.ts diff --git a/backend/src/libs/test-utils/mocks/factories/dto/teamDto-factory.ts b/backend/src/libs/test-utils/mocks/factories/dto/teamDto-factory.ts new file mode 100644 index 000000000..dfa1c2451 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/teamDto-factory.ts @@ -0,0 +1,22 @@ +import faker from '@faker-js/faker'; +import { ForTeamDtoEnum, TeamDto } from 'src/modules/communication/dto/team.dto'; +import { BoardRoles } from 'src/modules/communication/dto/types'; +import { buildTestFactory } from '../generic-factory.mock'; +import { UserCommunicationDtoFactory } from './userCommunicationDto-factory.mock'; + +const mockTeamCommunicationDto = () => { + return { + name: faker.company.companyName(), + normalName: faker.company.companyName(), + boardId: faker.datatype.uuid(), + channelId: faker.datatype.uuid(), + type: faker.helpers.arrayElement([ForTeamDtoEnum.SUBTEAM, ForTeamDtoEnum.TEAM]), + for: faker.helpers.arrayElement([BoardRoles.MEMBER, BoardRoles.RESPONSIBLE]), + participants: UserCommunicationDtoFactory.createMany(2), + teamNumber: Math.random() * 10 + }; +}; + +export const TeamCommunicationDtoFactory = buildTestFactory(() => { + return mockTeamCommunicationDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/dto/userCommunicationDto-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/dto/userCommunicationDto-factory.mock.ts new file mode 100644 index 000000000..a8b1bfe80 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/userCommunicationDto-factory.mock.ts @@ -0,0 +1,19 @@ +import faker from '@faker-js/faker'; +import { UserDto } from 'src/modules/communication/dto/user.dto'; +import { buildTestFactory } from '../generic-factory.mock'; + +const mockUserCommunicationDto = () => { + return { + id: faker.datatype.uuid(), + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + email: faker.internet.email(), + responsible: faker.datatype.boolean(), + boardId: faker.datatype.uuid(), + slackId: faker.datatype.uuid() + }; +}; + +export const UserCommunicationDtoFactory = buildTestFactory(() => { + return mockUserCommunicationDto(); +}); 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 dbc342f17..280b2ff15 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -45,6 +45,7 @@ import { UserFactory } from 'src/libs/test-utils/mocks/factories/user-factory'; import User from 'src/modules/users/entities/user.schema'; import { generateNewSubColumns } from '../utils/generate-subcolumns'; import { mergeCardsFromSubBoardColumnsIntoMainBoard } from '../utils/merge-cards-from-subboard'; +import { TeamCommunicationDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/teamDto-factory'; describe('GetUpdateBoardService', () => { let boardService: UpdateBoardServiceInterface; @@ -56,6 +57,8 @@ describe('GetUpdateBoardService', () => { let slackSendMessageServiceMock: DeepMocked; let slackCommunicationServiceMock: DeepMocked; let deleteCardServiceMock: DeepMocked; + let createBoardUserServiceMock: DeepMocked; + let deleteBoardUserServiceMock: DeepMocked; let socketServiceMock: DeepMocked; @@ -132,6 +135,8 @@ describe('GetUpdateBoardService', () => { CommunicationsType.TYPES.services.SlackCommunicationService ); socketServiceMock = module.get(SocketGateway); + createBoardUserServiceMock = module.get(BoardUsers.TYPES.services.CreateBoardUserService); + deleteBoardUserServiceMock = module.get(BoardUsers.TYPES.services.DeleteBoardUserService); }); beforeEach(() => { @@ -349,7 +354,21 @@ describe('GetUpdateBoardService', () => { ); }); - it('should throw error if mergeBoards fails', async () => { + it('should throw error if updateMergedSubBoard fails', async () => { + const userId = faker.datatype.uuid(); + const board = BoardFactory.create({ isSubBoard: false }); + const subBoard = BoardFactory.create({ isSubBoard: true }); + + boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoard); + boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); + boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(null); + + expect(async () => await boardService.mergeBoards(subBoard._id, userId)).rejects.toThrowError( + BadRequestException + ); + }); + + it('should throw error if updateMergedBoard fails', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false }); const subBoard = BoardFactory.create({ isSubBoard: true }); @@ -365,12 +384,13 @@ describe('GetUpdateBoardService', () => { it('should call executeMergeBoardNotification if board has slackChannelId and slackEnable', async () => { const userId = faker.datatype.uuid(); + const subBoard = BoardFactory.create({ isSubBoard: true }); const board = BoardFactory.create({ isSubBoard: false, slackEnable: true, - slackChannelId: faker.datatype.uuid() + slackChannelId: faker.datatype.uuid(), + dividedBoards: [subBoard] }); - const subBoard = BoardFactory.create({ isSubBoard: true }); boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoard); boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); @@ -430,6 +450,98 @@ describe('GetUpdateBoardService', () => { }); }); + describe('updateChannelId', () => { + it('should call board repository', async () => { + const teamsDto = TeamCommunicationDtoFactory.createMany(2); + const board = BoardFactory.create(); + + boardRepositoryMock.updatedChannelId.mockResolvedValue(board); + boardService.updateChannelId(teamsDto); + + expect(boardRepositoryMock.updatedChannelId).toBeCalledTimes(teamsDto.length); + }); + }); + + describe('updateBoardParticipants', () => { + it('should throw error when insert board users fails', async () => { + const addUsers = BoardUserDtoFactory.createMany(3); + const removedUsers = []; + + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce([]); + expect( + async () => await boardService.updateBoardParticipants(addUsers, removedUsers) + ).rejects.toThrowError(BadRequestException); + }); + + it('should throw error when delete board users fails', async () => { + const addUsers = BoardUserDtoFactory.createMany(3); + const boardUserToRemove = BoardUserFactory.create(); + const removedUsers = [boardUserToRemove._id]; + + deleteBoardUserServiceMock.deleteBoardUsers.mockResolvedValueOnce(0); + expect( + async () => await boardService.updateBoardParticipants(addUsers, removedUsers) + ).rejects.toThrowError(BadRequestException); + }); + + it('should return boardUsers created', async () => { + const addUsers = BoardUserDtoFactory.createMany(2); + const boardUserToRemove = BoardUserFactory.create(); + const removedUsers = [boardUserToRemove._id]; + const saveBoardUsersResult = BoardUserFactory.createMany(2, [ + { + _id: addUsers[0]._id, + role: addUsers[0].role, + user: addUsers[0].user, + board: addUsers[0].board + }, + { + _id: addUsers[1]._id, + role: addUsers[1].role, + user: addUsers[1].user, + board: addUsers[1].board + } + ]); + + createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce(saveBoardUsersResult); + deleteBoardUserServiceMock.deleteBoardUsers.mockResolvedValueOnce(1); + + const boardUsersCreatedResult = await boardService.updateBoardParticipants( + addUsers, + removedUsers + ); + expect(boardUsersCreatedResult).toEqual(saveBoardUsersResult); + }); + }); + + describe('updateBoardParticipantsRole', () => { + it('should throw error if updateBoardUserRole fails', async () => { + const boardUserDto = BoardUserDtoFactory.create(); + + updateBoardUserServiceMock.updateBoardUserRole.mockResolvedValueOnce(null); + + expect( + async () => await boardService.updateBoardParticipantsRole(boardUserDto) + ).rejects.toThrowError(BadRequestException); + }); + + it('should return boardUser role updated', async () => { + const boardUserDto = BoardUserDtoFactory.create({ role: BoardRoles.MEMBER }); + const boardUserUpdated = BoardUserFactory.create({ + _id: boardUserDto._id, + role: BoardRoles.RESPONSIBLE, + user: boardUserDto.user, + board: boardUserDto.board + }); + + updateBoardUserServiceMock.updateBoardUserRole.mockResolvedValueOnce(boardUserUpdated); + + const boardUserResult = await boardService.updateBoardParticipantsRole(boardUserDto); + + expect(boardUserResult).toEqual(boardUserUpdated); + }); + }); + describe('updatePhase', () => { it('should be defined', () => { expect(boardService.updatePhase).toBeDefined(); @@ -493,5 +605,38 @@ describe('GetUpdateBoardService', () => { message: expect.stringContaining('https://split.kigroup.de/') }); }); + + it('should call slackSendMessageService.execute once with slackMessageDto', async () => { + const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; + // Create a fake board object with the specified properties + const board = BoardFactory.create(); + board.team = TeamFactory.create({ name: 'xgeeks' }); + + board.phase = BoardPhases.VOTINGPHASE; + board.slackEnable = true; + + const table = { + [SLACK_MASTER_CHANNEL_ID]: '6405f9a04633b1668f71c068', + [SLACK_ENABLE]: true, + [FRONTEND_URL]: 'https://split.kigroup.de/' + }; + + // Set up the board repository mock to resolve with the fake board object + boardRepositoryMock.updatePhase.mockResolvedValue(board); + + // Set up the configuration service mock + configServiceMock.getOrThrow.mockImplementation((key: string) => { + return table[key]; + }); + + // Call the service method being tested + await boardService.updatePhase(boardPhaseDto); + + // Verify that the slackSendMessageService.execute method with correct data 1 time + expect(slackSendMessageServiceMock.execute).toHaveBeenNthCalledWith(1, { + slackChannelId: '6405f9a04633b1668f71c068', + message: expect.stringContaining('https://split.kigroup.de/') + }); + }); }); }); diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index 7b075a8f7..f81140d6f 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -192,7 +192,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { ); if (!updatedMergedSubBoard) { - throw new BadRequestException(UPDATE_FAILED); + throw new Error(UPDATE_FAILED); } const mergedBoard = await this.boardRepository.updateMergedBoard( @@ -202,7 +202,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { ); if (!mergedBoard) { - throw new BadRequestException(UPDATE_FAILED); + throw new Error(UPDATE_FAILED); } if (board.slackChannelId && board.slackEnable) { @@ -257,17 +257,17 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { async updateBoardParticipantsRole(boardUserToUpdateRole: BoardUserDto) { const user = boardUserToUpdateRole.user as User; - const updatedBoardUsers = await this.updateBoardUserService.updateBoardUserRole( + const updatedBoardUser = await this.updateBoardUserService.updateBoardUserRole( boardUserToUpdateRole.board, user._id, boardUserToUpdateRole.role ); - if (!updatedBoardUsers) { + if (!updatedBoardUser) { throw new BadRequestException(UPDATE_FAILED); } - return updatedBoardUsers; + return updatedBoardUser; } async updatePhase(boardPhaseDto: BoardPhaseDto) { @@ -406,10 +406,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { (colBoard) => colBoard._id.toString() === col._id.toString() ); - if (columnBoard) { - return [{ ...columnBoard, title: col.title }]; - } - if (boardData.deletedColumns) { const columnToDelete = boardData.deletedColumns.some( (colId) => colId === col._id.toString() @@ -419,6 +415,10 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { return []; } } + + if (columnBoard) { + return [{ ...columnBoard, title: col.title }]; + } } return [{ ...col }]; diff --git a/backend/src/modules/communication/applications/slack-communication.application.ts b/backend/src/modules/communication/applications/slack-communication.application.ts index d470925a9..84ea816a7 100644 --- a/backend/src/modules/communication/applications/slack-communication.application.ts +++ b/backend/src/modules/communication/applications/slack-communication.application.ts @@ -1,6 +1,6 @@ import { Logger } from '@nestjs/common'; import { get_nth_suffix } from 'src/libs/utils/ordinal-date'; -import { TeamDto } from 'src/modules/communication/dto/team.dto'; +import { ForTeamDtoEnum, TeamDto } from 'src/modules/communication/dto/team.dto'; import { BoardRoles, BoardType, ConfigurationType } from 'src/modules/communication/dto/types'; import { UserDto } from 'src/modules/communication/dto/user.dto'; import { BoardNotValidError } from 'src/modules/communication/errors/board-not-valid.error'; @@ -195,7 +195,7 @@ export class SlackCommunicationApplication implements CommunicationApplicationIn name: board.title, normalName: normalizeName(board.team.name), boardId: board.id, - type: board.isSubBoard ? 'sub-team' : 'team', + type: board.isSubBoard ? ForTeamDtoEnum.SUBTEAM : ForTeamDtoEnum.TEAM, for: BoardRoles.RESPONSIBLE, teamNumber: 0, participants: board.isSubBoard @@ -219,7 +219,7 @@ export class SlackCommunicationApplication implements CommunicationApplicationIn name: subBoard.title, normalName: normalizeName(board.team.name + '-' + subBoard.title.replace(' board', '')), boardId: subBoard.id, - type: 'sub-team', + type: ForTeamDtoEnum.SUBTEAM, for: BoardRoles.MEMBER, participants, teamNumber: subBoard.boardNumber diff --git a/backend/src/modules/communication/dto/team.dto.ts b/backend/src/modules/communication/dto/team.dto.ts index 009e5b6a1..f19f82f84 100644 --- a/backend/src/modules/communication/dto/team.dto.ts +++ b/backend/src/modules/communication/dto/team.dto.ts @@ -1,6 +1,11 @@ import { BoardRoles } from 'src/modules/communication/dto/types'; import { UserDto } from 'src/modules/communication/dto/user.dto'; +export enum ForTeamDtoEnum { + TEAM = 'team', + SUBTEAM = 'sub-team' +} + export class TeamDto { name!: string; @@ -10,7 +15,7 @@ export class TeamDto { channelId?: string; - type!: 'team' | 'sub-team'; + type!: ForTeamDtoEnum; for!: BoardRoles; From 0dd7df25c47fd3aaf8b2ef8e34a17f53939606e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Fri, 17 Mar 2023 09:31:33 +0000 Subject: [PATCH 09/14] refactor: change description of some tests --- .../services/update.board.service.spec.ts | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) 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 280b2ff15..b9a55603b 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -149,10 +149,6 @@ describe('GetUpdateBoardService', () => { }); describe('update', () => { - it('should be defined', () => { - expect(boardService.update).toBeDefined(); - }); - it('should throw error if max votes is less than the highest votes on board', async () => { const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: 2 }); const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 3 }, { votesCount: 1 }]); @@ -229,7 +225,7 @@ describe('GetUpdateBoardService', () => { ); }); - it('should call socketService if socketId exists', async () => { + it('should call socketService.sendUpdatedBoard if socketId exists', async () => { const board = BoardFactory.create(); const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null, @@ -247,7 +243,7 @@ describe('GetUpdateBoardService', () => { expect(socketServiceMock.sendUpdatedBoard).toBeCalledTimes(1); }); - it('should call handleResponsiblesSlackMessage if board has a newResponsible and slack enable', async () => { + it('should call slackCommunicationService.executeResponsibleChange if board has a newResponsible and slack enable', async () => { const board = BoardFactory.create({ isSubBoard: true, slackEnable: true, @@ -286,7 +282,6 @@ describe('GetUpdateBoardService', () => { boardRepositoryMock.getBoard.mockResolvedValueOnce(board); boardRepositoryMock.updateBoard.mockResolvedValueOnce(board); - //gets current responsible from the board jest .spyOn(getBoardUserServiceMock, 'getBoardResponsible') .mockResolvedValue(currentResponsible); @@ -296,12 +291,13 @@ describe('GetUpdateBoardService', () => { expect(slackCommunicationServiceMock.executeResponsibleChange).toBeCalledTimes(1); }); - it('should update board', async () => { - const board = BoardFactory.create(); + it('should update a split board', async () => { + const board = BoardFactory.create({ addCards: false }); const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null, title: 'Mock 2.0', - _id: board._id + _id: board._id, + addCards: true }); const boardResult = { ...board, title: updateBoardDto.title }; @@ -313,7 +309,7 @@ describe('GetUpdateBoardService', () => { expect(result).toEqual(boardResult); }); - it('should update board from a regular board', async () => { + it('should update regular board', async () => { const board = BoardFactory.create({ isSubBoard: false, dividedBoards: [] }); board.columns[1].title = 'Make things'; @@ -354,7 +350,7 @@ describe('GetUpdateBoardService', () => { ); }); - it('should throw error if updateMergedSubBoard fails', async () => { + it('should throw error if boardRepository.updateMergedSubBoard fails', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false }); const subBoard = BoardFactory.create({ isSubBoard: true }); @@ -368,7 +364,7 @@ describe('GetUpdateBoardService', () => { ); }); - it('should throw error if updateMergedBoard fails', async () => { + it('should throw error if boardRepository.updateMergedBoard fails', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false }); const subBoard = BoardFactory.create({ isSubBoard: true }); @@ -382,7 +378,7 @@ describe('GetUpdateBoardService', () => { ); }); - it('should call executeMergeBoardNotification if board has slackChannelId and slackEnable', async () => { + it('should call slackCommunicationService.executeMergeBoardNotification if board has slackChannelId and slackEnable', async () => { const userId = faker.datatype.uuid(); const subBoard = BoardFactory.create({ isSubBoard: true }); const board = BoardFactory.create({ @@ -402,7 +398,7 @@ describe('GetUpdateBoardService', () => { expect(slackCommunicationServiceMock.executeMergeBoardNotification).toBeCalledTimes(1); }); - it('should call socketService if there is a socketId', async () => { + it('should call socketService.sendUpdatedAllBoard if there is a socketId', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false, @@ -451,7 +447,7 @@ describe('GetUpdateBoardService', () => { }); describe('updateChannelId', () => { - it('should call board repository', async () => { + it('should call boardRepository.updatedChannelId', async () => { const teamsDto = TeamCommunicationDtoFactory.createMany(2); const board = BoardFactory.create(); From 21c5369b4c45d17ab8fc51b59a5b27fe34a4f4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Fri, 17 Mar 2023 09:56:25 +0000 Subject: [PATCH 10/14] fix: handling inserMany error --- .../services/update.board.service.spec.ts | 8 +----- .../boards/services/update.board.service.ts | 25 +++---------------- 2 files changed, 5 insertions(+), 28 deletions(-) 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 b9a55603b..966685578 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -1,11 +1,9 @@ import { updateBoardService } from './../boards.providers'; import { Test, TestingModule } from '@nestjs/testing'; -import { getTeamService } from 'src/modules/teams/providers'; import { boardRepository } from '../boards.providers'; import SocketGateway from 'src/modules/socket/gateway/socket.gateway'; import { DeepMocked, createMock } from '@golevelup/ts-jest'; import { UpdateBoardServiceInterface } from '../interfaces/services/update.board.service.interface'; -import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface'; import { BoardRepositoryInterface } from '../repositories/board.repository.interface'; import { DeleteCardServiceInterface } from 'src/modules/cards/interfaces/services/delete.card.service.interface'; import { deleteCardService } from 'src/modules/cards/cards.providers'; @@ -66,10 +64,6 @@ describe('GetUpdateBoardService', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ updateBoardService, - { - provide: getTeamService.provide, - useValue: createMock() - }, { provide: CommunicationsType.TYPES.services.SlackSendMessageService, useValue: createMock() @@ -463,7 +457,7 @@ describe('GetUpdateBoardService', () => { const addUsers = BoardUserDtoFactory.createMany(3); const removedUsers = []; - createBoardUserServiceMock.saveBoardUsers.mockResolvedValueOnce([]); + createBoardUserServiceMock.saveBoardUsers.mockRejectedValueOnce('Error inserting users'); expect( async () => await boardService.updateBoardParticipants(addUsers, removedUsers) ).rejects.toThrowError(BadRequestException); diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index f81140d6f..30a34061d 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -1,19 +1,11 @@ import { UpdateBoardUserServiceInterface } from './../../boardusers/interfaces/services/update.board.user.service.interface'; import BoardUserDto from 'src/modules/boards/dto/board.user.dto'; -import { - BadRequestException, - Inject, - Injectable, - NotFoundException, - forwardRef -} from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { getIdFromObjectId } from 'src/libs/utils/getIdFromObjectId'; import isEmpty from 'src/libs/utils/isEmpty'; import { TeamDto } from 'src/modules/communication/dto/team.dto'; import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface'; import * as CommunicationsType from 'src/modules/communication/interfaces/types'; -import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface'; -import * as Teams from 'src/modules/teams/interfaces/types'; import * as Cards from 'src/modules/cards/interfaces/types'; import * as Boards from 'src/modules/boards/interfaces/types'; import * as BoardUsers from 'src/modules/boardusers/interfaces/types'; @@ -23,7 +15,7 @@ import { ResponsibleType } from '../interfaces/responsible.interface'; import { UpdateBoardServiceInterface } from '../interfaces/services/update.board.service.interface'; import Board from '../entities/board.schema'; import BoardUser from '../entities/board.user.schema'; -import { DELETE_FAILED, INSERT_FAILED, UPDATE_FAILED } from 'src/libs/exceptions/messages'; +import { DELETE_FAILED, UPDATE_FAILED } from 'src/libs/exceptions/messages'; import SocketGateway from 'src/modules/socket/gateway/socket.gateway'; import Column from '../../columns/entities/column.schema'; import ColumnDto from '../../columns/dto/column.dto'; @@ -49,8 +41,6 @@ import { mergeCardsFromSubBoardColumnsIntoMainBoard } from '../utils/merge-cards @Injectable() export default class UpdateBoardService implements UpdateBoardServiceInterface { constructor( - @Inject(forwardRef(() => Teams.TYPES.services.GetTeamService)) - private getTeamService: GetTeamServiceInterface, @Inject(CommunicationsType.TYPES.services.SlackCommunicationService) private slackCommunicationService: CommunicationServiceInterface, @Inject(CommunicationsType.TYPES.services.SlackSendMessageService) @@ -244,7 +234,8 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { try { let createdBoardUsers: BoardUser[] = []; - if (addUsers.length > 0) createdBoardUsers = await this.addBoardUsers(addUsers); + if (addUsers.length > 0) + createdBoardUsers = await this.createBoardUserService.saveBoardUsers(addUsers); if (removeUsers.length > 0) await this.deleteBoardUsers(removeUsers); @@ -502,14 +493,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { } } - private async addBoardUsers(boardUsers: BoardUserDto[]) { - const createdBoardUsers = await this.createBoardUserService.saveBoardUsers(boardUsers); - - if (createdBoardUsers.length < 1) throw new Error(INSERT_FAILED); - - return createdBoardUsers; - } - private async deleteBoardUsers(boardUsers: string[]) { const deletedCount = await this.deleteBoardUserService.deleteBoardUsers(boardUsers); From 7b5aa81b50f331f20444bd0698626102c554847d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Fri, 17 Mar 2023 10:00:17 +0000 Subject: [PATCH 11/14] fix: import boardusers type name --- .../src/modules/boards/services/update.board.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 966685578..b5fd8fb2d 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -10,7 +10,7 @@ import { deleteCardService } from 'src/modules/cards/cards.providers'; import * as CommunicationsType from 'src/modules/communication/interfaces/types'; import * as Boards from 'src/modules/boards/interfaces/types'; import * as Cards from 'src/modules/cards/interfaces/types'; -import * as BoardUsers from 'src/modules/boardUsers/interfaces/types'; +import * as BoardUsers from 'src/modules/boardusers/interfaces/types'; import { CommunicationServiceInterface } from 'src/modules/communication/interfaces/slack-communication.service.interface'; import { SendMessageServiceInterface } from 'src/modules/communication/interfaces/send-message.service.interface'; import { EventEmitter2 } from '@nestjs/event-emitter'; From 1243cd4599bd2f9a8ada63f86646faf19e022982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Fri, 17 Mar 2023 10:26:42 +0000 Subject: [PATCH 12/14] fix: remove async from updateBoardPhase on controller --- backend/src/modules/boards/controller/boards.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/modules/boards/controller/boards.controller.ts b/backend/src/modules/boards/controller/boards.controller.ts index 00d1e1560..c271bfdff 100644 --- a/backend/src/modules/boards/controller/boards.controller.ts +++ b/backend/src/modules/boards/controller/boards.controller.ts @@ -361,7 +361,7 @@ export default class BoardsController { type: UnauthorizedResponse }) @Put(':boardId/phase') - async updateBoardPhase(@Body() boardPhaseDto: BoardPhaseDto) { + updateBoardPhase(@Body() boardPhaseDto: BoardPhaseDto) { this.updateBoardApp.updatePhase(boardPhaseDto); } } From 2d8cd73a41c8b86410d0ebe26952b94b81276981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Mon, 20 Mar 2023 11:06:52 +0000 Subject: [PATCH 13/14] fix: change BadRequestError to UpdateFailedError --- .../services/update.board.service.spec.ts | 184 ++++++++++-------- .../boards/services/update.board.service.ts | 35 ++-- 2 files changed, 125 insertions(+), 94 deletions(-) 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 b5fd8fb2d..d64456dcf 100644 --- a/backend/src/modules/boards/services/update.board.service.spec.ts +++ b/backend/src/modules/boards/services/update.board.service.spec.ts @@ -18,7 +18,7 @@ import { ConfigService } from '@nestjs/config'; import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory.mock'; 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 { BadRequestException, NotFoundException } from '@nestjs/common'; +import { NotFoundException } from '@nestjs/common'; import { BoardUserRepositoryInterface } from 'src/modules/boardusers/interfaces/repositories/board-user.repository.interface'; import { boardUserRepository, @@ -44,6 +44,7 @@ import User from 'src/modules/users/entities/user.schema'; import { generateNewSubColumns } from '../utils/generate-subcolumns'; import { mergeCardsFromSubBoardColumnsIntoMainBoard } from '../utils/merge-cards-from-subboard'; import { TeamCommunicationDtoFactory } from 'src/libs/test-utils/mocks/factories/dto/teamDto-factory'; +import { UpdateFailedException } from 'src/libs/exceptions/updateFailedBadRequestException'; describe('GetUpdateBoardService', () => { let boardService: UpdateBoardServiceInterface; @@ -143,18 +144,18 @@ describe('GetUpdateBoardService', () => { }); describe('update', () => { - it('should throw error if max votes is less than the highest votes on board', async () => { + it('should throw an error if max votes is less than the highest votes on board', async () => { const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: 2 }); const boardUsers = BoardUserFactory.createMany(2, [{ votesCount: 3 }, { votesCount: 1 }]); jest.spyOn(getBoardUserServiceMock, 'getVotesCount').mockResolvedValueOnce(boardUsers); expect(async () => await boardService.update('1', updateBoardDto)).rejects.toThrow( - BadRequestException + UpdateFailedException ); }); - it('should throw error if board not found', async () => { + it('should throw an error if board not found', async () => { const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); boardRepositoryMock.getBoard.mockResolvedValue(null); @@ -163,7 +164,7 @@ describe('GetUpdateBoardService', () => { ); }); - it('should call changeResponsibleOnBoard if current responsible is not equal to new responsible', async () => { + it('should call the changeResponsibleOnBoard method if the current responsible is not equal to the new responsible', async () => { const board = BoardFactory.create({ isSubBoard: true }); const mainBoard = BoardFactory.create(); const currentResponsible = BoardUserFactory.create({ @@ -197,17 +198,15 @@ describe('GetUpdateBoardService', () => { boardRepositoryMock.getBoard.mockResolvedValueOnce(board); - //gets current responsible from the board - jest - .spyOn(getBoardUserServiceMock, 'getBoardResponsible') - .mockResolvedValue(currentResponsible); + //gets the current responsible from the board + getBoardUserServiceMock.getBoardResponsible.mockResolvedValueOnce(currentResponsible); await boardService.update(board._id, updateBoardDto); - //update changeResponsibleOnBoard + //update the changeResponsibleOnBoard expect(updateBoardUserServiceMock.updateBoardUserRole).toBeCalled(); }); - it('should throw error when update fails', async () => { + it('should throw an error when update fails', async () => { const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null }); const board = BoardFactory.create(); @@ -215,11 +214,11 @@ describe('GetUpdateBoardService', () => { boardRepositoryMock.updateBoard.mockResolvedValueOnce(null); expect(async () => await boardService.update('1', updateBoardDto)).rejects.toThrow( - BadRequestException + UpdateFailedException ); }); - it('should call socketService.sendUpdatedBoard if socketId exists', async () => { + it('should call the socketService.sendUpdatedBoard if the socketId exists', async () => { const board = BoardFactory.create(); const updateBoardDto = UpdateBoardDtoFactory.create({ maxVotes: null, @@ -237,7 +236,7 @@ describe('GetUpdateBoardService', () => { expect(socketServiceMock.sendUpdatedBoard).toBeCalledTimes(1); }); - it('should call slackCommunicationService.executeResponsibleChange if board has a newResponsible and slack enable', async () => { + it('should call the slackCommunicationService.executeResponsibleChange if the board has a newResponsible and slack enable', async () => { const board = BoardFactory.create({ isSubBoard: true, slackEnable: true, @@ -303,7 +302,7 @@ describe('GetUpdateBoardService', () => { expect(result).toEqual(boardResult); }); - it('should update regular board', async () => { + it('should update a regular board', async () => { const board = BoardFactory.create({ isSubBoard: false, dividedBoards: [] }); board.columns[1].title = 'Make things'; @@ -319,6 +318,7 @@ describe('GetUpdateBoardService', () => { }); boardRepositoryMock.getBoard.mockResolvedValueOnce(board); + getBoardUserServiceMock.getBoardResponsible.mockResolvedValueOnce(null); deleteCardServiceMock.deleteCardVotesFromColumn.mockResolvedValue(null); const boardResult = { ...board, columns: board.columns.slice(1) }; @@ -332,7 +332,7 @@ describe('GetUpdateBoardService', () => { }); describe('mergeBoards', () => { - it('should throw error when subBoard, board or subBoard.submittedByUser are undefined', async () => { + it('should throw an error when the subBoard, board or subBoard.submittedByUser are undefined', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false }); @@ -340,11 +340,11 @@ describe('GetUpdateBoardService', () => { boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); expect(async () => await boardService.mergeBoards('-1', userId)).rejects.toThrowError( - BadRequestException + NotFoundException ); }); - it('should throw error if boardRepository.updateMergedSubBoard fails', async () => { + it('should throw an error if the boardRepository.updateMergedSubBoard fails', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false }); const subBoard = BoardFactory.create({ isSubBoard: true }); @@ -354,45 +354,73 @@ describe('GetUpdateBoardService', () => { boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(null); expect(async () => await boardService.mergeBoards(subBoard._id, userId)).rejects.toThrowError( - BadRequestException + UpdateFailedException ); }); - it('should throw error if boardRepository.updateMergedBoard fails', async () => { + it('should throw an error if the boardRepository.updateMergedBoard fails', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false }); const subBoard = BoardFactory.create({ isSubBoard: true }); boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoard); boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); - boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(null); + boardRepositoryMock.updateMergedBoard.mockResolvedValueOnce(null); expect(async () => await boardService.mergeBoards(subBoard._id, userId)).rejects.toThrowError( - BadRequestException + UpdateFailedException ); }); - it('should call slackCommunicationService.executeMergeBoardNotification if board has slackChannelId and slackEnable', async () => { + it('should call the slackCommunicationService.executeMergeBoardNotification if the board has slackChannelId and slackEnable', async () => { const userId = faker.datatype.uuid(); - const subBoard = BoardFactory.create({ isSubBoard: true }); + const subBoards = BoardFactory.createMany(2, [ + { isSubBoard: true, boardNumber: 1, submitedByUser: userId, submitedAt: new Date() }, + { isSubBoard: true, boardNumber: 2 } + ]); const board = BoardFactory.create({ isSubBoard: false, slackEnable: true, slackChannelId: faker.datatype.uuid(), - dividedBoards: [subBoard] + dividedBoards: subBoards }); - boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoard); + boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoards[1]); boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); - boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(subBoard); - boardRepositoryMock.updateMergedBoard.mockResolvedValueOnce(board); - await boardService.mergeBoards(subBoard._id, userId); + //mocks update of subBoard that is being merged + const subBoardUpdated = { ...subBoards[1], submitedByUser: userId, submitedAt: new Date() }; + boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(subBoardUpdated); + + //merges columns of the sub-boards to the main board + const newSubColumnsSubBoard_1 = generateNewSubColumns(subBoards[0]); + const newSubColumnsSubBoard_2 = generateNewSubColumns(subBoardUpdated); + + const mergeSubBoard_1 = { + ...board, + columns: mergeCardsFromSubBoardColumnsIntoMainBoard( + [...board.columns], + newSubColumnsSubBoard_1 + ) + }; + + const boardResult = { + ...mergeSubBoard_1, + columns: mergeCardsFromSubBoardColumnsIntoMainBoard( + [...board.columns], + newSubColumnsSubBoard_2 + ), + dividedBoards: [subBoards[0], subBoardUpdated] + }; + + boardRepositoryMock.updateMergedBoard.mockResolvedValueOnce(boardResult); + + await boardService.mergeBoards(subBoards[1]._id, userId); expect(slackCommunicationServiceMock.executeMergeBoardNotification).toBeCalledTimes(1); }); - it('should call socketService.sendUpdatedAllBoard if there is a socketId', async () => { + it('should call the socketService.sendUpdatedAllBoard if there is a socketId', async () => { const userId = faker.datatype.uuid(); const board = BoardFactory.create({ isSubBoard: false, @@ -412,36 +440,57 @@ describe('GetUpdateBoardService', () => { expect(socketServiceMock.sendUpdatedAllBoard).toBeCalledTimes(1); }); - it('should return merged board', async () => { + it('should return the merged board', async () => { const userId = faker.datatype.uuid(); + const subBoards = BoardFactory.createMany(2, [ + { isSubBoard: true, boardNumber: 1 }, + { isSubBoard: true, boardNumber: 2 } + ]); const board = BoardFactory.create({ - isSubBoard: false + isSubBoard: false, + slackEnable: true, + slackChannelId: faker.datatype.uuid(), + dividedBoards: subBoards }); - const subBoard = BoardFactory.create({ isSubBoard: true }); - const newSubColumns = generateNewSubColumns(subBoard); - boardRepositoryMock.getBoard.mockResolvedValue(subBoard); - boardRepositoryMock.getBoardByQuery.mockResolvedValue(board); + boardRepositoryMock.getBoard.mockResolvedValueOnce(subBoards[0]); + boardRepositoryMock.getBoardByQuery.mockResolvedValueOnce(board); //mocks update of subBoard that is being merged - const subBoardUpdated = { ...subBoard, submitedByUser: userId, submitedAt: new Date() }; + const subBoardUpdated = { ...subBoards[0], submitedByUser: userId, submitedAt: new Date() }; boardRepositoryMock.updateMergedSubBoard.mockResolvedValueOnce(subBoardUpdated); - //mocks update to the mainBoard to merge cards from subBoard - const boardResult = { + //merges columns of the sub-boards to the main board + const newSubColumnsSubBoard_1 = generateNewSubColumns(subBoards[1]); + const newSubColumnsSubBoard_2 = generateNewSubColumns(subBoardUpdated); + + const mergeSubBoard_1 = { ...board, - columns: mergeCardsFromSubBoardColumnsIntoMainBoard([...board.columns], newSubColumns) + columns: mergeCardsFromSubBoardColumnsIntoMainBoard( + [...board.columns], + newSubColumnsSubBoard_1 + ) }; + + const boardResult = { + ...mergeSubBoard_1, + columns: mergeCardsFromSubBoardColumnsIntoMainBoard( + [...board.columns], + newSubColumnsSubBoard_2 + ), + dividedBoards: [subBoardUpdated, subBoards[1]] + }; + boardRepositoryMock.updateMergedBoard.mockResolvedValueOnce(boardResult); - const result = await boardService.mergeBoards(subBoard._id, userId); + const result = await boardService.mergeBoards(subBoards[0]._id, userId); expect(result).toEqual(boardResult); }); }); describe('updateChannelId', () => { - it('should call boardRepository.updatedChannelId', async () => { + it('should call the boardRepository.updatedChannelId', async () => { const teamsDto = TeamCommunicationDtoFactory.createMany(2); const board = BoardFactory.create(); @@ -453,17 +502,17 @@ describe('GetUpdateBoardService', () => { }); describe('updateBoardParticipants', () => { - it('should throw error when insert board users fails', async () => { + it('should throw an error when the inserting of board users fails', async () => { const addUsers = BoardUserDtoFactory.createMany(3); const removedUsers = []; createBoardUserServiceMock.saveBoardUsers.mockRejectedValueOnce('Error inserting users'); expect( async () => await boardService.updateBoardParticipants(addUsers, removedUsers) - ).rejects.toThrowError(BadRequestException); + ).rejects.toThrowError(UpdateFailedException); }); - it('should throw error when delete board users fails', async () => { + it('should throw an error when the deleting of board users fails', async () => { const addUsers = BoardUserDtoFactory.createMany(3); const boardUserToRemove = BoardUserFactory.create(); const removedUsers = [boardUserToRemove._id]; @@ -471,10 +520,10 @@ describe('GetUpdateBoardService', () => { deleteBoardUserServiceMock.deleteBoardUsers.mockResolvedValueOnce(0); expect( async () => await boardService.updateBoardParticipants(addUsers, removedUsers) - ).rejects.toThrowError(BadRequestException); + ).rejects.toThrowError(UpdateFailedException); }); - it('should return boardUsers created', async () => { + it('should return the created boardUsers', async () => { const addUsers = BoardUserDtoFactory.createMany(2); const boardUserToRemove = BoardUserFactory.create(); const removedUsers = [boardUserToRemove._id]; @@ -505,17 +554,17 @@ describe('GetUpdateBoardService', () => { }); describe('updateBoardParticipantsRole', () => { - it('should throw error if updateBoardUserRole fails', async () => { + it('should throw an error if the updateBoardUserRole fails', async () => { const boardUserDto = BoardUserDtoFactory.create(); updateBoardUserServiceMock.updateBoardUserRole.mockResolvedValueOnce(null); expect( async () => await boardService.updateBoardParticipantsRole(boardUserDto) - ).rejects.toThrowError(BadRequestException); + ).rejects.toThrowError(UpdateFailedException); }); - it('should return boardUser role updated', async () => { + it('should return the boardUser with the updated role', async () => { const boardUserDto = BoardUserDtoFactory.create({ role: BoardRoles.MEMBER }); const boardUserUpdated = BoardUserFactory.create({ _id: boardUserDto._id, @@ -537,24 +586,24 @@ describe('GetUpdateBoardService', () => { expect(boardService.updatePhase).toBeDefined(); }); - it('should call boardRepository ', async () => { + it('should call the boardRepository ', async () => { const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; await boardService.updatePhase(boardPhaseDto); expect(boardRepositoryMock.updatePhase).toBeCalledTimes(1); }); - it('should throw badRequestException when boardRepository fails', async () => { + it('should throw the badRequestException when the boardRepository fails', async () => { const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; // Set up the board repository mock to reject with an error boardRepositoryMock.updatePhase.mockRejectedValueOnce(new Error('Some error')); - // Verify that the service method being tested throws a BadRequestException + // Verify that the service method that is being tested throws a BadRequestException expect(async () => await boardService.updatePhase(boardPhaseDto)).rejects.toThrowError( - BadRequestException + UpdateFailedException ); }); - it('should call websocket with eventEmitter', async () => { + it('should call the websocket with eventEmitter', async () => { const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; // Call the service method being tested await boardService.updatePhase(boardPhaseDto); @@ -563,7 +612,7 @@ describe('GetUpdateBoardService', () => { expect(eventEmitterMock.emit).toHaveBeenCalledTimes(1); }); - it('should call slackSendMessageService.execute once with slackMessageDto', async () => { + it('should call the slackSendMessageService.execute once with slackMessageDto', async () => { const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; // Create a fake board object with the specified properties const board = BoardFactory.create(); @@ -594,32 +643,9 @@ describe('GetUpdateBoardService', () => { slackChannelId: '6405f9a04633b1668f71c068', message: expect.stringContaining('https://split.kigroup.de/') }); - }); - - it('should call slackSendMessageService.execute once with slackMessageDto', async () => { - const boardPhaseDto = { boardId: '6405f9a04633b1668f71c068', phase: BoardPhases.ADDCARDS }; - // Create a fake board object with the specified properties - const board = BoardFactory.create(); - board.team = TeamFactory.create({ name: 'xgeeks' }); board.phase = BoardPhases.VOTINGPHASE; - board.slackEnable = true; - - const table = { - [SLACK_MASTER_CHANNEL_ID]: '6405f9a04633b1668f71c068', - [SLACK_ENABLE]: true, - [FRONTEND_URL]: 'https://split.kigroup.de/' - }; - - // Set up the board repository mock to resolve with the fake board object - boardRepositoryMock.updatePhase.mockResolvedValue(board); - // Set up the configuration service mock - configServiceMock.getOrThrow.mockImplementation((key: string) => { - return table[key]; - }); - - // Call the service method being tested await boardService.updatePhase(boardPhaseDto); // Verify that the slackSendMessageService.execute method with correct data 1 time diff --git a/backend/src/modules/boards/services/update.board.service.ts b/backend/src/modules/boards/services/update.board.service.ts index 30a34061d..73d34d5ef 100644 --- a/backend/src/modules/boards/services/update.board.service.ts +++ b/backend/src/modules/boards/services/update.board.service.ts @@ -1,6 +1,6 @@ import { UpdateBoardUserServiceInterface } from './../../boardusers/interfaces/services/update.board.user.service.interface'; import BoardUserDto from 'src/modules/boards/dto/board.user.dto'; -import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common'; import { getIdFromObjectId } from 'src/libs/utils/getIdFromObjectId'; import isEmpty from 'src/libs/utils/isEmpty'; import { TeamDto } from 'src/modules/communication/dto/team.dto'; @@ -15,7 +15,6 @@ import { ResponsibleType } from '../interfaces/responsible.interface'; import { UpdateBoardServiceInterface } from '../interfaces/services/update.board.service.interface'; import Board from '../entities/board.schema'; import BoardUser from '../entities/board.user.schema'; -import { DELETE_FAILED, UPDATE_FAILED } from 'src/libs/exceptions/messages'; import SocketGateway from 'src/modules/socket/gateway/socket.gateway'; import Column from '../../columns/entities/column.schema'; import ColumnDto from '../../columns/dto/column.dto'; @@ -37,9 +36,12 @@ import { CreateBoardUserServiceInterface } from 'src/modules/boardusers/interfac import { DeleteBoardUserServiceInterface } from 'src/modules/boardusers/interfaces/services/delete.board.user.service.interface'; import { generateNewSubColumns } from '../utils/generate-subcolumns'; import { mergeCardsFromSubBoardColumnsIntoMainBoard } from '../utils/merge-cards-from-subboard'; +import { UpdateFailedException } from 'src/libs/exceptions/updateFailedBadRequestException'; @Injectable() export default class UpdateBoardService implements UpdateBoardServiceInterface { + private logger = new Logger(UpdateBoardService.name); + constructor( @Inject(CommunicationsType.TYPES.services.SlackCommunicationService) private slackCommunicationService: CommunicationServiceInterface, @@ -74,7 +76,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { const highestVotes = await this.getHighestVotesOnBoard(boardId); if (highestVotes > Number(boardData.maxVotes)) { - throw new BadRequestException( + throw new UpdateFailedException( `You can't set a lower value to max votes. Please insert a value higher or equals than ${highestVotes}!` ); } @@ -135,7 +137,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { const updatedBoard = await this.boardRepository.updateBoard(boardId, board, true); - if (!updatedBoard) throw new BadRequestException(UPDATE_FAILED); + if (!updatedBoard) throw new UpdateFailedException(); if (boardData.socketId) { this.socketService.sendUpdatedBoard(boardId, boardData.socketId); @@ -168,10 +170,11 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { this.boardRepository.getBoardByQuery({ dividedBoards: { $in: [subBoardId] } }) ]); - if (!subBoard || !board || subBoard.submitedByUser) - throw new BadRequestException(UPDATE_FAILED); + if (!subBoard || !board || subBoard.submitedByUser) { + throw new NotFoundException('Board or subBoard not found'); + } - const columnsWitMergedCards = this.getColumnsFromMainBoardWithMergedCards(subBoard, board); + const columnsWithMergedCards = this.getColumnsFromMainBoardWithMergedCards(subBoard, board); await this.boardRepository.startTransaction(); try { @@ -182,17 +185,19 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { ); if (!updatedMergedSubBoard) { - throw new Error(UPDATE_FAILED); + this.logger.error('Update of the subBoard to be merged failed'); + throw new UpdateFailedException(); } const mergedBoard = await this.boardRepository.updateMergedBoard( board._id, - columnsWitMergedCards, + columnsWithMergedCards, true ); if (!mergedBoard) { - throw new Error(UPDATE_FAILED); + this.logger.error('Update of the merged board failed'); + throw new UpdateFailedException(); } if (board.slackChannelId && board.slackEnable) { @@ -221,7 +226,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { await this.boardRepository.endSession(); } - throw new BadRequestException(UPDATE_FAILED); + throw new UpdateFailedException(); } updateChannelId(teams: TeamDto[]) { @@ -241,7 +246,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { return createdBoardUsers; } catch (error) { - throw new BadRequestException(UPDATE_FAILED); + throw new UpdateFailedException(); } } @@ -255,7 +260,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { ); if (!updatedBoardUser) { - throw new BadRequestException(UPDATE_FAILED); + throw new UpdateFailedException(); } return updatedBoardUser; @@ -283,7 +288,7 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { this.slackSendMessageService.execute(slackMessageDto); } } catch (err) { - throw new BadRequestException(UPDATE_FAILED); + throw new UpdateFailedException(); } } @@ -496,6 +501,6 @@ export default class UpdateBoardService implements UpdateBoardServiceInterface { private async deleteBoardUsers(boardUsers: string[]) { const deletedCount = await this.deleteBoardUserService.deleteBoardUsers(boardUsers); - if (deletedCount <= 0) throw new Error(DELETE_FAILED); + if (deletedCount <= 0) throw new UpdateFailedException(); } } From 13528eb2ba1f98d9b58e19b9f989782b46b10d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1tia=20Antunes?= Date: Mon, 20 Mar 2023 12:23:10 +0000 Subject: [PATCH 14/14] fix: add path to mergeBoards on board repo --- backend/src/modules/boards/repositories/board.repository.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/modules/boards/repositories/board.repository.ts b/backend/src/modules/boards/repositories/board.repository.ts index 187385149..c177e6a35 100644 --- a/backend/src/modules/boards/repositories/board.repository.ts +++ b/backend/src/modules/boards/repositories/board.repository.ts @@ -173,7 +173,10 @@ export class BoardRepository $set: { columns: newColumns } }, { new: true }, - null, + { + path: 'dividedBoards', + select: 'submitedByUser' + }, withSession ); }