diff --git a/backend/src/libs/guards/getBoardPermissions.guard.ts b/backend/src/libs/guards/getBoardPermissions.guard.ts index f3a4ee03d..7748d9194 100644 --- a/backend/src/libs/guards/getBoardPermissions.guard.ts +++ b/backend/src/libs/guards/getBoardPermissions.guard.ts @@ -29,13 +29,13 @@ export class GetBoardGuard implements CanActivate { try { const { isPublic, team } = await this.getBoardService.getBoardData(boardId); - const boardUserFound = await this.getBoardService.getBoardUsers(boardId, userId); + const boardUserFound = await this.getBoardService.getBoardUser(boardId, userId); - if (isPublic || boardUserFound.length || isSAdmin) { + if (isPublic || boardUserFound || isSAdmin) { return true; } - if (!boardUserFound.length) { + if (!boardUserFound) { const { role: teamRole } = (team as Team).users.find( (teamUser: TeamUser) => (teamUser.user as User)._id.toString() === userId.toString() ); 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 e6c84fb41..ee24f7782 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 @@ -7,7 +7,7 @@ const userId = faker.datatype.uuid(); const mockBoardData = () => { return { - _id: faker.datatype.uuid(), + _id: faker.database.mongodbObjectId(), title: faker.lorem.words(), columns: ColumnFactory.createMany(3), isPublic: faker.datatype.boolean(), diff --git a/backend/src/libs/test-utils/mocks/factories/boardUser-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/boardUser-factory.mock.ts new file mode 100644 index 000000000..64c18969e --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/boardUser-factory.mock.ts @@ -0,0 +1,18 @@ +import faker from '@faker-js/faker'; +import { buildTestFactory } from './generic-factory.mock'; +import BoardUser from 'src/modules/boards/entities/board.user.schema'; +import { BoardRoles } from 'src/libs/enum/board.roles'; + +const mockBoardUserData = () => { + return { + _id: faker.database.mongodbObjectId(), + role: faker.helpers.arrayElement([BoardRoles.MEMBER, BoardRoles.RESPONSIBLE]), + user: faker.database.mongodbObjectId(), + board: faker.database.mongodbObjectId(), + votesCount: 0 + }; +}; + +export const BoardUserFactory = buildTestFactory(() => { + return mockBoardUserData(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/card-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/card-factory.mock.ts index 63cdbdcea..a8eca06b3 100644 --- a/backend/src/libs/test-utils/mocks/factories/card-factory.mock.ts +++ b/backend/src/libs/test-utils/mocks/factories/card-factory.mock.ts @@ -1,25 +1,26 @@ import faker from '@faker-js/faker'; import Card from 'src/modules/cards/entities/card.schema'; import { buildTestFactory } from './generic-factory.mock'; +import { UserFactory } from './user-factory'; -const userId = faker.datatype.uuid(); const cardId = faker.datatype.uuid(); -const cardText = faker.lorem.words(); -const commentText = faker.lorem.paragraph(1); +const cardText = faker.lorem.words(5); +const commentText = faker.lorem.words(4); const teamId = faker.datatype.uuid(); const createdAtDate = faker.datatype.datetime(); +const user = UserFactory.create(); const mockCardData = (): Card => { return { _id: cardId, text: cardText, - createdBy: userId, + createdBy: user, createdByTeam: teamId, createdAt: createdAtDate, comments: [ { text: commentText, - createdBy: userId, + createdBy: user, anonymous: false } ], @@ -29,11 +30,11 @@ const mockCardData = (): Card => { { _id: cardId, text: cardText, - createdBy: userId, + createdBy: user, comments: [ { text: commentText, - createdBy: userId, + createdBy: user, anonymous: false } ], 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.ts new file mode 100644 index 000000000..4b58a02c0 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/dto/userDto-factory.ts @@ -0,0 +1,21 @@ +import faker from '@faker-js/faker'; +import UserDto from 'src/modules/users/dto/user.dto'; +import { buildTestFactory } from '../generic-factory.mock'; + +const mockUserDto = () => { + return { + _id: faker.database.mongodbObjectId(), + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + email: faker.internet.email(), + strategy: faker.lorem.word(), + isSAdmin: faker.datatype.boolean(), + isAnonimous: faker.datatype.boolean(), + providerAccountCreatedAt: faker.date.past(1), + avatar: faker.internet.avatar() + }; +}; + +export const UserDtoFactory = buildTestFactory(() => { + return mockUserDto(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/team-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/team-factory.mock.ts new file mode 100644 index 000000000..6cd40527a --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/team-factory.mock.ts @@ -0,0 +1,18 @@ +import faker from '@faker-js/faker'; +import { buildTestFactory } from './generic-factory.mock'; +import Team from 'src/modules/teams/entities/teams.schema'; + +const dateCreatedAt = faker.date.past(1); + +const mockTeamData = () => { + return { + _id: faker.database.mongodbObjectId(), + name: faker.company.companyName(), + createdAt: dateCreatedAt, + upddatedAt: faker.date.between(dateCreatedAt, Date.now()) + }; +}; + +export const TeamFactory = buildTestFactory(() => { + return mockTeamData(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/teamUser-factory.mock.ts b/backend/src/libs/test-utils/mocks/factories/teamUser-factory.mock.ts new file mode 100644 index 000000000..15b722461 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/teamUser-factory.mock.ts @@ -0,0 +1,18 @@ +import faker from '@faker-js/faker'; +import { buildTestFactory } from './generic-factory.mock'; +import { TeamRoles } from 'src/libs/enum/team.roles'; +import TeamUser from 'src/modules/teams/entities/team.user.schema'; + +const mockTeamUserData = () => { + return { + _id: faker.database.mongodbObjectId(), + role: faker.helpers.arrayElement([TeamRoles.MEMBER, TeamRoles.ADMIN, TeamRoles.STAKEHOLDER]), + isNewJoiner: faker.datatype.boolean(), + user: faker.database.mongodbObjectId(), + team: faker.database.mongodbObjectId() + }; +}; + +export const TeamUserFactory = buildTestFactory(() => { + return mockTeamUserData(); +}); diff --git a/backend/src/libs/test-utils/mocks/factories/user-factory.ts b/backend/src/libs/test-utils/mocks/factories/user-factory.ts new file mode 100644 index 000000000..c2d1b4bc1 --- /dev/null +++ b/backend/src/libs/test-utils/mocks/factories/user-factory.ts @@ -0,0 +1,22 @@ +import faker from '@faker-js/faker'; +import { buildTestFactory } from './generic-factory.mock'; +import User from 'src/modules/users/entities/user.schema'; + +const mockUserData = () => { + return { + _id: faker.database.mongodbObjectId(), + firstName: faker.name.firstName(), + lastName: faker.name.lastName(), + password: faker.internet.password(), + email: faker.internet.email(), + joinedAt: faker.date.past(1), + strategy: faker.lorem.word(), + isSAdmin: faker.datatype.boolean(), + isDeleted: faker.datatype.boolean(), + isAnonymous: faker.datatype.boolean() + }; +}; + +export const UserFactory = buildTestFactory(() => { + return mockUserData(); +}); diff --git a/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts b/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts index e64d4be1a..20f0af87b 100644 --- a/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts +++ b/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts @@ -52,7 +52,7 @@ export interface GetBoardServiceInterface { getBoardData(boardId: string): Promise; - getBoardUsers(board: string, user: string): Promise; + getBoardUser(board: string, user: string): Promise; getAllMainBoards(): Promise; } diff --git a/backend/src/modules/boards/repositories/board-user.repository.interface.ts b/backend/src/modules/boards/repositories/board-user.repository.interface.ts index a2ce91857..ff9b9b2fa 100644 --- a/backend/src/modules/boards/repositories/board-user.repository.interface.ts +++ b/backend/src/modules/boards/repositories/board-user.repository.interface.ts @@ -8,8 +8,8 @@ export interface BoardUserRepositoryInterface extends BaseInterfaceRepository; getBoardResponsible(boardId: string): Promise; getVotesCount(boardId: string): Promise; - getBoardUsers(board: string, user: string): Promise; getBoardUser(board: string, user: string): Promise; + getBoardUserPopulated(board: string, user: string): Promise; createBoardUsers(boardUsers: BoardUserDto[]): Promise; updateBoardUserRole(boardId: string, userId: string, role: string): Promise; updateVoteUser( diff --git a/backend/src/modules/boards/repositories/board-user.repository.ts b/backend/src/modules/boards/repositories/board-user.repository.ts index b2f1ebfa5..e51a3b37d 100644 --- a/backend/src/modules/boards/repositories/board-user.repository.ts +++ b/backend/src/modules/boards/repositories/board-user.repository.ts @@ -32,11 +32,11 @@ export class BoardUserRepository return this.findAllWithQuery({ board: boardId }, ['votesCount']); } - getBoardUsers(board: string, user: string) { - return this.findAllWithQuery({ board, user }); + getBoardUser(board: string, user: string) { + return this.findOneByField({ board, user }); } - getBoardUser(board: string, user: string) { + getBoardUserPopulated(board: string, user: string) { return this.findOneByFieldWithQuery({ board, user }, null, { path: 'user', select: '_id firstName lastName ' 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 5d44bdd7e..009c51e3b 100644 --- a/backend/src/modules/boards/services/get.board.service.spec.ts +++ b/backend/src/modules/boards/services/get.board.service.spec.ts @@ -1,150 +1,554 @@ -import { EventEmitter2 } from '@nestjs/event-emitter'; -import jwtService from 'src/libs/test-utils/mocks/jwtService.mock'; -import { ConfigService } from '@nestjs/config'; -import { boardUserRepository, createBoardUserService } from './../boards.providers'; -import { JwtService } from '@nestjs/jwt'; +import { + boardUserRepository, + createBoardUserService, + getBoardService +} from './../boards.providers'; import { getTokenAuthService } from './../../auth/auth.providers'; -import { Logger } from '@nestjs/common'; -import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; -import { Document, LeanDocument } from 'mongoose'; import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory.mock'; -import Board from 'src/modules/boards/entities/board.schema'; -import { - getTeamService, - teamRepository, - teamUserRepository, - updateTeamService -} from 'src/modules/teams/providers'; -import { updateUserService, userRepository } from 'src/modules/users/users.providers'; -import { boardRepository, getBoardService } from '../boards.providers'; -import { cleanBoard } from '../utils/clean-board'; +import { getTeamService } from 'src/modules/teams/providers'; +import { updateUserService } from 'src/modules/users/users.providers'; +import { boardRepository } from '../boards.providers'; import SocketGateway from 'src/modules/socket/gateway/socket.gateway'; -import GetBoardService from 'src/modules/boards/services/get.board.service'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const fakeBoards = BoardFactory.createMany(2); +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import * as Boards from 'src/modules/boards/interfaces/types'; +import * as Teams from 'src/modules/teams/interfaces/types'; +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 { 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'; +import { Tokens } from 'src/libs/interfaces/jwt/tokens.interface'; +import { CreateBoardUserServiceInterface } from '../interfaces/services/create.board.user.service.interface'; +import { UpdateUserServiceInterface } from 'src/modules/users/interfaces/services/update.user.service.interface'; +import { BoardRepositoryInterface } from '../repositories/board.repository.interface'; +import { BoardUserRepositoryInterface } from '../repositories/board-user.repository.interface'; +import { GetBoardServiceInterface } from '../interfaces/services/get.board.service.interface'; describe('GetBoardService', () => { - let service: GetBoardService; + let boardService: GetBoardServiceInterface; + let boardRepositoryMock: DeepMocked; + let boardUserRepositoryMock: DeepMocked; + let getTeamServiceMock: DeepMocked; + let getTokenAuthServiceMock: DeepMocked; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - createBoardUserService, - updateUserService, - ConfigService, - getTokenAuthService, - getTeamService, getBoardService, - teamUserRepository, - teamRepository, - boardRepository, - updateTeamService, - userRepository, - boardUserRepository, - SocketGateway, - EventEmitter2, { - provide: getModelToken('Team'), - useValue: {} + provide: getTeamService.provide, + useValue: createMock() }, { - provide: getModelToken('TeamUser'), - useValue: {} + provide: createBoardUserService.provide, + useValue: createMock() }, { - provide: getModelToken(Board.name), - useValue: {} + provide: getTokenAuthService.provide, + useValue: createMock() }, { - provide: getModelToken('BoardUser'), - useValue: {} + provide: updateUserService.provide, + useValue: createMock() }, { - provide: getModelToken('User'), - useValue: {} + provide: boardRepository.provide, + useValue: createMock() }, { - provide: getModelToken('ResetPassword'), - useValue: {} + provide: boardUserRepository.provide, + useValue: createMock() }, { - provide: JwtService, - useValue: jwtService - }, - GetBoardService + provide: SocketGateway, + useValue: createMock() + } ] }).compile(); - service = module.get(GetBoardService); - jest.spyOn(Logger.prototype, 'error').mockImplementation(jest.fn); + boardService = module.get(getBoardService.provide); + boardRepositoryMock = module.get(Boards.TYPES.repositories.BoardRepository); + boardUserRepositoryMock = module.get(Boards.TYPES.repositories.BoardUserRepository); + getTeamServiceMock = module.get(Teams.TYPES.services.GetTeamService); + getTokenAuthServiceMock = module.get(Auth.TYPES.services.GetTokenAuthService); + }); + + beforeEach(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); }); it('should be defined', () => { - expect(service).toBeDefined(); + expect(boardService).toBeDefined(); }); - it('should keep votes only for one user by userId', () => { - const boardGiven = { - columns: [ - { - cards: [ - { - items: [ - { - votes: ['any_id_1', 'any_id_2'] - } - ], - votes: ['any_id_1'] - } - ] - }, - { - cards: [ - { - items: [ - { - votes: [] - }, - { - votes: [] - } - ], - votes: ['any_id_4'] - } - ] + describe('getAllBoardIdsAndTeamIdsOfUser', () => { + it('should be defined', () => { + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeDefined(); + }); + + it('should call boardUserRepository and getTeamService', async () => { + const userId = faker.datatype.uuid(); + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + expect(boardUserRepositoryMock.getAllBoardsIdsOfUser).toBeCalledTimes(1); + expect(boardUserRepositoryMock.getAllBoardsIdsOfUser).toBeCalledWith(userId); + + expect(getTeamServiceMock.getTeamsOfUser).toBeCalledTimes(1); + expect(getTeamServiceMock.getTeamsOfUser).toBeCalledWith(userId); + }); + + it('should return boardIds and teamIds', async () => { + const userId = faker.datatype.uuid(); + const boardUsers = BoardUserFactory.createMany(3); + const teams = TeamFactory.createMany(2).map((team) => { + return { ...team, boardsCount: 0 }; + }); + const boardIds = boardUsers.map((boardUser) => boardUser.board); + const teamIds = teams.map((team) => team._id); + const boardAndTeamIdsResult = { boardIds, teamIds }; + + boardUserRepositoryMock.getAllBoardsIdsOfUser.mockResolvedValue(boardUsers); + getTeamServiceMock.getTeamsOfUser.mockResolvedValue(teams); + + const result = await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + expect(result).toEqual(boardAndTeamIdsResult); + }); + }); + + describe('getUserBoardsOfLast3Months', () => { + it('should be defined', () => { + expect(boardService.getUserBoardsOfLast3Months).toBeDefined(); + }); + + it('should call getAllBoardsIdsAndTeamIdsOfUser', async () => { + const userId = faker.datatype.uuid(); + + jest.spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser'); + + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledTimes(1); + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledWith(userId); + }); + + it('should return all the boards of the last 3 months', async () => { + const boards = BoardFactory.createMany(3); + const userId = faker.datatype.uuid(); + const teamIds = [TeamFactory.create()._id]; + boards[0].team = teamIds[0]; + const boardIds = boards.map((board) => board._id); + const getBoardAndTeamIdsResult = { boardIds, teamIds }; + const filterBoardsResponse = boards.filter( + (board) => + board.isSubBoard && (boardIds.includes(board._id) || teamIds.includes(String(board.team))) + ); + const boardsOf3LastMonths = { boards: filterBoardsResponse, hasNextPage: false, page: 1 }; + + //mock response from getAllBoardIdAndTeamIdsOfUSer + jest + .spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser') + .mockResolvedValueOnce(getBoardAndTeamIdsResult); + + //call function getAllBoardIdAndTeamIdsOfUSer + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + //mock returned values from calls to repo on private function getBoards + await boardRepositoryMock.getCountPage.mockResolvedValue(1); + + //the mock result is filtered with the same query that it's passed as argument + await boardRepositoryMock.getAllBoards.mockResolvedValue(filterBoardsResponse); + + const result = await boardService.getUserBoardsOfLast3Months(userId, 1); + + expect(result).toEqual(boardsOf3LastMonths); + }); + }); + describe('getSuperAdminBoards', () => { + it('should be defined', () => { + expect(boardService.getSuperAdminBoards).toBeDefined(); + }); + + it('should call getAllBoardsIdsAndTeamIdsOfUser', async () => { + const userId = faker.datatype.uuid(); + + jest.spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser'); + + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledTimes(1); + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledWith(userId); + }); + + it('should return all boards for the superAdmin', async () => { + const boards = BoardFactory.createMany(4); + const teams = TeamFactory.createMany(2); + const userId = faker.datatype.uuid(); + + const teamIds = teams.map((team) => team._id); + const boardIds = boards.map((board) => board._id); + const getBoardAndTeamIdsResult = { boardIds, teamIds }; + + boards[2].isSubBoard = false; + boards[3].isSubBoard = false; + boards[0].team = teamIds[0]; + const filterBoardsResponse = boards.filter( + (board) => + !board.isSubBoard && + (boardIds.includes(board._id) || teamIds.includes(String(board.team))) + ); + const allBoards = { boards: filterBoardsResponse, hasNextPage: false, page: 1 }; + + //mock response from getAllBoardIdAndTeamIdsOfUSer + jest + .spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser') + .mockResolvedValueOnce(getBoardAndTeamIdsResult); + + //call function getAllBoardIdAndTeamIdsOfUSer + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + //mock returned values from calls to repo on private function getBoards + await boardRepositoryMock.getCountPage.mockResolvedValue(1); + await boardRepositoryMock.getAllBoards.mockResolvedValue(filterBoardsResponse); + + const result = await boardService.getSuperAdminBoards(userId, 1); + + expect(result).toEqual(allBoards); + }); + }); + + describe('getUsersBoards', () => { + it('should be defined', () => { + expect(boardService.getUsersBoards).toBeDefined(); + }); + + it('should call getAllBoardsIdsAndTeamIdsOfUser', async () => { + const userId = faker.datatype.uuid(); + + jest.spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser'); + + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledTimes(1); + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledWith(userId); + }); + + it('should return all boards of a user', async () => { + const boards = BoardFactory.createMany(4); + const teams = TeamFactory.createMany(2); + const userId = faker.datatype.uuid(); + + const teamIds = teams.map((team) => team._id); + const boardIds = boards.map((board) => board._id); + const getBoardAndTeamIdsResult = { boardIds, teamIds }; + + boards[0].isSubBoard = false; + boards[2].isSubBoard = false; + boards[1].team = teamIds[1]; + const filterBoardsResponse = boards.filter( + (board) => + !board.isSubBoard && + (boardIds.includes(board._id) || teamIds.includes(String(board.team))) + ); + const allBoards = { boards: filterBoardsResponse, hasNextPage: false, page: 1 }; + + //mock response from getAllBoardIdAndTeamIdsOfUSer + jest + .spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser') + .mockResolvedValueOnce(getBoardAndTeamIdsResult); + + //call function getAllBoardIdAndTeamIdsOfUSer + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + //mock returned values from calls to repo on private function getBoards + await boardRepositoryMock.getCountPage.mockResolvedValue(1); + await boardRepositoryMock.getAllBoards.mockResolvedValue(filterBoardsResponse); + + const result = await boardService.getUsersBoards(userId, 1); + + expect(result).toEqual(allBoards); + }); + }); + + describe('getTeamBoards', () => { + it('should be defined', () => { + expect(boardService.getTeamBoards).toBeDefined(); + }); + + it('should return all boards from a team', async () => { + const boards = BoardFactory.createMany(4); + const team = TeamFactory.create(); + const userId = faker.datatype.uuid(); + + boards[0].isSubBoard = false; + boards[2].isSubBoard = false; + boards[1].team = team._id; + const filterBoardsResponse = boards.filter( + (board) => !board.isSubBoard || String(board.team) === team._id + ); + const allBoards = { boards: filterBoardsResponse, hasNextPage: false, page: 1 }; + + //mock returned values from calls to repo on private function getBoards + await boardRepositoryMock.getCountPage.mockResolvedValue(1); + await boardRepositoryMock.getAllBoards.mockResolvedValue(filterBoardsResponse); + + const result = await boardService.getTeamBoards(userId, 1); + + expect(result).toEqual(allBoards); + }); + }); + + describe('getPersonalUserBoards', () => { + it('should be defined', () => { + expect(boardService.getPersonalUserBoards).toBeDefined(); + }); + + it('should call getAllBoardsIdsAndTeamIdsOfUser', async () => { + const userId = faker.datatype.uuid(); + + jest.spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser'); + + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledTimes(1); + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledWith(userId); + }); + + it('should return all personal boards of a user', async () => { + const boards = BoardFactory.createMany(4); + const teams = TeamFactory.createMany(1); + const userId = faker.datatype.uuid(); + + const teamIds = teams.map((team) => team._id); + const boardIds = boards.map((board) => board._id); + const getBoardAndTeamIdsResult = { boardIds, teamIds }; + + boards[0].isSubBoard = false; + boards[2].isSubBoard = false; + boards[0].team = null; + boards[1].team = null; + boards[2].team = teamIds[0]; + boards[3].team = teamIds[0]; + const filterBoardsResponse = boards.filter( + (board) => !board.isSubBoard && !board.team && boardIds.includes(board._id) + ); + const allBoards = { boards: filterBoardsResponse, hasNextPage: false, page: 1 }; + + //mock response from getAllBoardIdAndTeamIdsOfUSer + jest + .spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser') + .mockResolvedValueOnce(getBoardAndTeamIdsResult); + + //call function getAllBoardIdAndTeamIdsOfUSer + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + //mock returned values from calls to repo on private function getBoards + await boardRepositoryMock.getCountPage.mockResolvedValue(1); + await boardRepositoryMock.getAllBoards.mockResolvedValue(filterBoardsResponse); + + const result = await boardService.getPersonalUserBoards(userId, 1); + + expect(result).toEqual(allBoards); + }); + }); + + describe('getBoard', () => { + it('should be defined', () => { + expect(boardService.getBoard).toBeDefined(); + }); + + it('should call boardRepository', async () => { + const board = BoardFactory.create(); + + await boardRepositoryMock.getBoardData(board._id); + + expect(boardRepositoryMock.getBoardData).toBeCalledTimes(1); + expect(boardRepositoryMock.getBoardData).toBeCalledWith(board._id); + }); + + it('should throw error if board is not found ', async () => { + const userDtoMock = UserDtoFactory.create(); + + boardRepositoryMock.getBoardData.mockResolvedValue(null); + expect(async () => await boardService.getBoard('-1', userDtoMock)).rejects.toThrow( + NotFoundException + ); + }); + + it('should return board and main board if is a subBoard', async () => { + const mainBoard = BoardFactory.create(); + mainBoard.isSubBoard = false; + const subBoard = BoardFactory.create(); + subBoard.isSubBoard = true; + const boardUser = BoardUserFactory.create(); + boardUser.board = subBoard._id; + + const userDtoMock = UserDtoFactory.create(); + userDtoMock._id = String(boardUser.user); + + boardRepositoryMock.getBoardData.mockResolvedValue(subBoard); + + boardUserRepositoryMock.getBoardUser.mockResolvedValue(boardUser); + + boardRepositoryMock.getMainBoard.mockResolvedValue(mainBoard); + + const boardResult = await boardService.getBoard(subBoard._id, userDtoMock); + + const response = { board: subBoard, mainBoard }; + + expect(boardResult).toEqual(response); + }); + + it('should return board and guestUser if is a guestUser', async () => { + const board = BoardFactory.create(); + board.isSubBoard = false; + board.isPublic = true; + + const userDtoMock = UserDtoFactory.create(); + userDtoMock.isSAdmin = false; + userDtoMock.isAnonymous = true; + + const boardUser = BoardUserFactory.create(); + + const tokens: Tokens = { + accessToken: { + expiresIn: String(faker.datatype.number()), + token: faker.lorem.word() }, - { - cards: [] + refreshToken: { + expiresIn: String(faker.datatype.number()), + token: faker.lorem.word() } - ], - hideVotes: true - } as unknown as LeanDocument & { _id: any }>; - const userId = 'any_id_1'; + }; - const result = cleanBoard(boardGiven as Board, userId); + boardRepositoryMock.getBoardData.mockResolvedValue(board); - expect(result).toMatchObject({ - columns: [ - { - cards: [ - { - items: [ - { - votes: [] - } - ], - votes: [] - } - ] - }, - { - cards: [{ items: [{ votes: [] }, { votes: [] }], votes: [] }] - }, - { cards: [] } - ], - hideVotes: true + boardUserRepositoryMock.getBoardUser.mockResolvedValue(null); + + getTokenAuthServiceMock.getTokens.mockResolvedValue(tokens); + + boardUserRepositoryMock.getBoardUserPopulated.mockResolvedValue(boardUser); + + const boardResponse = { + guestUser: { accessToken: tokens.accessToken, user: userDtoMock._id }, + board + }; + + const boardResult = await boardService.getBoard(board._id, userDtoMock); + + expect(boardResult).toEqual(boardResponse); + }); + + it('should return a board when boardUserIsFound', async () => { + const board = BoardFactory.create(); + board.isSubBoard = false; + board.isPublic = false; + + const userDtoMock = UserDtoFactory.create(); + userDtoMock.isSAdmin = false; + userDtoMock.isAnonymous = true; + + const boardUser = BoardUserFactory.create(); + + boardRepositoryMock.getBoardData.mockResolvedValue(board); + + boardUserRepositoryMock.getBoardUser.mockResolvedValue(boardUser); + + const boardResult = await boardService.getBoard(board._id, userDtoMock); + + expect(boardResult.board).toEqual(board); + }); + + it('should return a board when boardIsPublic, boardUser is not found and userDto is not anonymous', async () => { + const board = BoardFactory.create(); + board.isSubBoard = false; + board.isPublic = true; + + const userDtoMock = UserDtoFactory.create(); + userDtoMock.isSAdmin = false; + userDtoMock.isAnonymous = false; + + boardRepositoryMock.getBoardData.mockResolvedValue(board); + + boardUserRepositoryMock.getBoardUser.mockResolvedValue(null); + + const boardResult = await boardService.getBoard(board._id, userDtoMock); + + expect(boardResult.board).toEqual(board); + }); + }); + + describe('countBoards', () => { + it('should be defined', () => { + expect(boardService.countBoards).toBeDefined(); + }); + + it('should call getAllBoardsIdsAndTeamIdsOfUser', async () => { + const userId = faker.datatype.uuid(); + + jest.spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser'); + + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledTimes(1); + expect(boardService.getAllBoardIdsAndTeamIdsOfUser).toBeCalledWith(userId); + }); + + it('should call boardRepository', async () => { + const boards = BoardFactory.createMany(4); + const teams = TeamFactory.createMany(1); + const userId = faker.datatype.uuid(); + + const teamIds = teams.map((team) => team._id); + const boardIds = boards.map((board) => board._id); + const getBoardAndTeamIdsResult = { boardIds, teamIds }; + + jest + .spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser') + .mockResolvedValueOnce(getBoardAndTeamIdsResult); + + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + await boardRepositoryMock.countBoards(boardIds, teamIds); + + expect(boardRepositoryMock.countBoards).toBeCalledTimes(1); + expect(boardRepositoryMock.countBoards).toBeCalledWith(boardIds, teamIds); + }); + + it('should return count of boards', async () => { + const boards = BoardFactory.createMany(4); + boards[0].isSubBoard = false; + boards[1].isSubBoard = false; + boards[2].isSubBoard = false; + const teams = TeamFactory.createMany(2); + const userId = faker.datatype.uuid(); + + const teamIds = teams.map((team) => team._id); + boards[2].team = teamIds[0]; + const boardIds = boards.map((board) => board._id); + const getBoardAndTeamIdsResult = { boardIds, teamIds }; + + jest + .spyOn(boardService, 'getAllBoardIdsAndTeamIdsOfUser') + .mockResolvedValueOnce(getBoardAndTeamIdsResult); + + await boardService.getAllBoardIdsAndTeamIdsOfUser(userId); + + const countResult = boards.filter( + (board) => + !board.isSubBoard && + (boardIds.includes(board._id) || teamIds.includes(String(board.team))) + ).length; + + boardRepositoryMock.countBoards.mockResolvedValue(countResult); + + const result = await boardService.countBoards(userId); + + expect(result).toEqual(countResult); }); }); }); diff --git a/backend/src/modules/boards/services/get.board.service.ts b/backend/src/modules/boards/services/get.board.service.ts index 7ad55e966..f1a14c71b 100644 --- a/backend/src/modules/boards/services/get.board.service.ts +++ b/backend/src/modules/boards/services/get.board.service.ts @@ -7,7 +7,7 @@ import { NotFoundException, forwardRef } from '@nestjs/common'; -import { BOARDS_NOT_FOUND, BOARD_USER_NOT_FOUND, NOT_FOUND } from 'src/libs/exceptions/messages'; +import { BOARD_USER_NOT_FOUND, NOT_FOUND } from 'src/libs/exceptions/messages'; import { GetTeamServiceInterface } from 'src/modules/teams/interfaces/services/get.team.service.interface'; import * as Teams from 'src/modules/teams/interfaces/types'; import * as Users from 'src/modules/users/interfaces/types'; @@ -48,6 +48,18 @@ export default class GetBoardService implements GetBoardServiceInterface { private readonly logger = new Logger(GetBoardService.name); + async getAllBoardIdsAndTeamIdsOfUser(userId: string) { + const [boardIds, teamIds] = await Promise.all([ + this.boardUserRepository.getAllBoardsIdsOfUser(userId), + this.getTeamService.getTeamsOfUser(userId) + ]); + + return { + boardIds: boardIds.map((boardUser) => String(boardUser.board)), + teamIds: teamIds.map((team) => team._id) + }; + } + async getUserBoardsOfLast3Months(userId: string, page: number, size?: number) { const { boardIds, teamIds } = await this.getAllBoardIdsAndTeamIdsOfUser(userId); @@ -130,18 +142,6 @@ export default class GetBoardService implements GetBoardServiceInterface { return this.boardRepository.countBoards(boardIds, teamIds); } - async getAllBoardIdsAndTeamIdsOfUser(userId: string) { - const [boardIds, teamIds] = await Promise.all([ - this.boardUserRepository.getAllBoardsIdsOfUser(userId), - this.getTeamService.getTeamsOfUser(userId) - ]); - - return { - boardIds: boardIds.map((boardUser) => boardUser.board), - teamIds: teamIds.map((team) => team._id) - }; - } - getBoardPopulated(boardId: string) { return this.boardRepository.getBoardPopulated(boardId); } @@ -154,8 +154,8 @@ export default class GetBoardService implements GetBoardServiceInterface { return this.boardRepository.getBoardData(boardId); } - getBoardUsers(board: string, user: string) { - return this.boardUserRepository.getBoardUsers(board, user); + getBoardUser(board: string, user: string) { + return this.boardUserRepository.getBoardUser(board, user); } getAllMainBoards() { @@ -168,15 +168,10 @@ export default class GetBoardService implements GetBoardServiceInterface { const count = await this.boardRepository.getCountPage(query); const hasNextPage = page + 1 < Math.ceil(count / (allBoards ? count : size)); - try { - const boards = await this.boardRepository.getAllBoards(allBoards, query, page, size, count); - return { boards: boards ?? [], hasNextPage, page }; - } catch (e) { - this.logger.error(BOARDS_NOT_FOUND); - } + const boards = await this.boardRepository.getAllBoards(allBoards, query, page, size, count); - return { boards: [], hasNextPage, page }; + return { boards: boards ?? [], hasNextPage, page }; } private async createBoardUserAndSendAccessToken( @@ -192,7 +187,7 @@ export default class GetBoardService implements GetBoardServiceInterface { } private async getGuestBoardUser(board: string, user: string): Promise { - const userFound = await this.boardUserRepository.getBoardUser(board, user); + const userFound = await this.boardUserRepository.getBoardUserPopulated(board, user); if (!userFound) { throw new BadRequestException(BOARD_USER_NOT_FOUND); @@ -223,9 +218,9 @@ export default class GetBoardService implements GetBoardServiceInterface { { _id: boardId, isPublic }: Board, user: UserDto ) { - const boardUserFound = await this.getBoardUsers(boardId, user._id); + const boardUserFound = await this.getBoardUser(boardId, user._id); - return !boardUserFound.length && isPublic && !user.isSAdmin + return !boardUserFound && isPublic && !user.isSAdmin ? await this.createPublicBoardUsers(boardId, user) : undefined; } @@ -233,6 +228,7 @@ export default class GetBoardService implements GetBoardServiceInterface { private async createPublicBoardUsers(boardId: string, user: UserDto) { if (user.isAnonymous) { const guestUser = await this.createBoardUserAndSendAccessToken(boardId, user._id); + await this.sendGuestBoardUser(boardId, user._id); return guestUser;