Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: user use cases #1407

Merged
merged 19 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions backend/src/libs/guards/deleteUser.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import User from 'src/modules/users/entities/user.schema';

@Injectable()
export class DeleteUserGuard implements CanActivate {
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();

const user: User = request.user;
const userToDelete: string = request.params.userId;

// user requesting can't delete itself
return user._id !== userToDelete;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UpdateUserServiceInterface } from 'src/modules/users/interfaces/services/update.user.service.interface';
import { Inject, Injectable } from '@nestjs/common';
import { TYPES } from 'src/modules/users/interfaces/types';
import { ResetPasswordUseCaseInterface } from '../interfaces/applications/reset-password.use-case.interface';
import { UpdateUserServiceInterface } from 'src/modules/users/interfaces/services/update.user.service.interface';
import { UPDATE_FAILED } from 'src/libs/exceptions/messages';
import { InvalidTokenException } from '../exceptions/invalidTokenException';
import { UpdateFailedException } from 'src/libs/exceptions/updateFailedBadRequestException';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { DeepMocked, createMock } from '@golevelup/ts-jest';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { UserRepositoryInterface } from '../repository/user.repository.interface';
import { deleteUserUseCase } from '../users.providers';
import * as Users from 'src/modules/users/interfaces/types';
import { Test, TestingModule } from '@nestjs/testing';
import { DeleteTeamUserServiceInterface } from 'src/modules/teamUsers/interfaces/services/delete.team.user.service.interface';
import { GetTeamUserServiceInterface } from 'src/modules/teamUsers/interfaces/services/get.team.user.service.interface';
import { UserFactory } from 'src/libs/test-utils/mocks/factories/user-factory';
import faker from '@faker-js/faker';
import { DeleteFailedException } from 'src/libs/exceptions/deleteFailedBadRequestException';
import { DELETE_TEAM_USER_SERVICE, GET_TEAM_USER_SERVICE } from 'src/modules/teamUsers/constants';

const userId = faker.datatype.uuid();
const userDeleted = UserFactory.create({ _id: userId });
const teamsOfUser = faker.datatype.number();

describe('DeleteUserUseCase', () => {
let deleteUser: UseCase<string, boolean>;
let userRepositoryMock: DeepMocked<UserRepositoryInterface>;
let deleteTeamUserServiceMock: DeepMocked<DeleteTeamUserServiceInterface>;
let getTeamUserServiceMock: DeepMocked<GetTeamUserServiceInterface>;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
deleteUserUseCase,
{
provide: Users.TYPES.repository,
useValue: createMock<UserRepositoryInterface>()
},
{
provide: DELETE_TEAM_USER_SERVICE,
useValue: createMock<DeleteTeamUserServiceInterface>()
},
{
provide: GET_TEAM_USER_SERVICE,
useValue: createMock<GetTeamUserServiceInterface>()
}
]
}).compile();

deleteUser = module.get<UseCase<string, boolean>>(deleteUserUseCase.provide);

userRepositoryMock = module.get(Users.TYPES.repository);
deleteTeamUserServiceMock = module.get(DELETE_TEAM_USER_SERVICE);
getTeamUserServiceMock = module.get(GET_TEAM_USER_SERVICE);
});

beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

it('should be defined', () => {
expect(deleteUser).toBeDefined();
});

describe('execute', () => {
it('should return true', async () => {
userRepositoryMock.deleteUser.mockResolvedValue(userDeleted);
getTeamUserServiceMock.countTeamsOfUser.mockResolvedValue(teamsOfUser);
deleteTeamUserServiceMock.deleteTeamUsersOfUser.mockResolvedValue(teamsOfUser);
await expect(deleteUser.execute(userId)).resolves.toEqual(true);
});

it('should throw error when user is not deleted', async () => {
await expect(deleteUser.execute(userId)).rejects.toThrowError(DeleteFailedException);
});

it('should throw error when commitTransaction fails', async () => {
userRepositoryMock.deleteUser.mockResolvedValue(userDeleted);
getTeamUserServiceMock.countTeamsOfUser.mockResolvedValue(teamsOfUser);
deleteTeamUserServiceMock.deleteTeamUsersOfUser.mockResolvedValue(teamsOfUser);
userRepositoryMock.commitTransaction.mockRejectedValue(new Error());
await expect(deleteUser.execute(userId)).rejects.toThrowError(DeleteFailedException);
});
});
});
35 changes: 19 additions & 16 deletions backend/src/modules/users/applications/delete-user.use-case.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { DeleteUserUseCaseInterface } from '../interfaces/applications/delete-user.use-case.interface';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { Inject, Injectable } from '@nestjs/common';
import { TYPES } from '../interfaces/types';
import { DeleteTeamUserServiceInterface } from 'src/modules/teamUsers/interfaces/services/delete.team.user.service.interface';
import { GetTeamUserServiceInterface } from '../../teamUsers/interfaces/services/get.team.user.service.interface';
import UserDto from '../dto/user.dto';
import { UserRepositoryInterface } from '../repository/user.repository.interface';
import { DeleteFailedException } from 'src/libs/exceptions/deleteFailedBadRequestException';
import { DELETE_TEAM_USER_SERVICE, GET_TEAM_USER_SERVICE } from 'src/modules/teamUsers/constants';

@Injectable()
export class DeleteUserUseCase implements DeleteUserUseCaseInterface {
export class DeleteUserUseCase implements UseCase<string, boolean> {
constructor(
@Inject(TYPES.repository) private readonly userRepository: UserRepositoryInterface,
@Inject(DELETE_TEAM_USER_SERVICE)
Expand All @@ -18,23 +17,12 @@ export class DeleteUserUseCase implements DeleteUserUseCaseInterface {
private readonly getTeamUserService: GetTeamUserServiceInterface
) {}

async execute(user: UserDto, userId: string) {
if (user._id == userId) {
throw new DeleteFailedException();
}

async execute(userId: string) {
await this.userRepository.startTransaction();
await this.deleteTeamUserService.startTransaction();

try {
await this.deleteUser(userId, true);
const teamsOfUser = await this.getTeamUserService.countTeamsOfUser(userId);

if (teamsOfUser > 0) {
await this.deleteTeamUserService.deleteTeamUsersOfUser(userId, false);
}
await this.userRepository.commitTransaction();
await this.deleteTeamUserService.commitTransaction();
await this.deleteUserAndTeamUsers(userId);

return true;
} catch (e) {
Expand All @@ -47,6 +35,21 @@ export class DeleteUserUseCase implements DeleteUserUseCaseInterface {
throw new DeleteFailedException();
}

private async deleteUserAndTeamUsers(userId: string) {
try {
await this.deleteUser(userId, true);
const teamsOfUser = await this.getTeamUserService.countTeamsOfUser(userId);

if (teamsOfUser > 0) {
await this.deleteTeamUserService.deleteTeamUsersOfUser(userId, true);
}
await this.userRepository.commitTransaction();
await this.deleteTeamUserService.commitTransaction();
} catch (error) {
throw new DeleteFailedException();
}
}

private async deleteUser(userId: string, withSession: boolean) {
const result = await this.userRepository.deleteUser(userId, withSession);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { DeepMocked, createMock } from '@golevelup/ts-jest';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { UserRepositoryInterface } from '../repository/user.repository.interface';
import { getAllUsersWithTeamsUseCase } from '../users.providers';
import * as Users from 'src/modules/users/interfaces/types';
import { Test, TestingModule } from '@nestjs/testing';
import { GetUserServiceInterface } from '../interfaces/services/get.user.service.interface';
import { GetTeamUserServiceInterface } from 'src/modules/teamUsers/interfaces/services/get.team.user.service.interface';
import { UserFactory } from 'src/libs/test-utils/mocks/factories/user-factory';
import { TeamFactory } from 'src/libs/test-utils/mocks/factories/team-factory.mock';
import { UserWithTeams } from '../interfaces/type-user-with-teams';
import GetAllUsersWithTeamsUseCaseDto from '../dto/useCase/get-all-users-with-teams.use-case.dto';
import { GetAllUsersWithTeamsPresenter } from 'src/modules/users/presenter/get-all-users-with-teams.presenter';
import { sortTeamUserListAlphabetically } from '../utils/sortings';
import { GET_TEAM_USER_SERVICE } from 'src/modules/teamUsers/constants';

const users = UserFactory.createMany(10);
const teams = TeamFactory.createMany(5);

const usersWithTeams: UserWithTeams[] = users.map((user) => ({
user,
teams: [...teams.map((team) => team._id)]
}));

const getAllUsersWithTeamsProps: GetAllUsersWithTeamsUseCaseDto = {};

const getAllUsersWithTeamsResult: GetAllUsersWithTeamsPresenter = {
userWithTeams: sortTeamUserListAlphabetically(usersWithTeams),
userAmount: users.length,
hasNextPage: 0 + 1 < Math.ceil(users.length / 15),
page: 0
};

describe('GetAllUsersWithTeamsUseCase', () => {
let getAllUsersWithTeams: UseCase<GetAllUsersWithTeamsUseCaseDto, GetAllUsersWithTeamsPresenter>;
let userRepositoryMock: DeepMocked<UserRepositoryInterface>;
let getUserServiceMock: DeepMocked<GetUserServiceInterface>;
let getTeamUserServiceMock: DeepMocked<GetTeamUserServiceInterface>;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
getAllUsersWithTeamsUseCase,
{
provide: Users.TYPES.repository,
useValue: createMock<UserRepositoryInterface>()
},
{
provide: Users.TYPES.services.GetUserService,
useValue: createMock<GetUserServiceInterface>()
},
{
provide: GET_TEAM_USER_SERVICE,
useValue: createMock<GetTeamUserServiceInterface>()
}
]
}).compile();

getAllUsersWithTeams = module.get<
UseCase<GetAllUsersWithTeamsUseCaseDto, GetAllUsersWithTeamsPresenter>
>(getAllUsersWithTeamsUseCase.provide);

userRepositoryMock = module.get(Users.TYPES.repository);
getUserServiceMock = module.get(Users.TYPES.services.GetUserService);
getTeamUserServiceMock = module.get(GET_TEAM_USER_SERVICE);
});

beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

it('should be defined', () => {
expect(getAllUsersWithTeams).toBeDefined();
});

describe('execute', () => {
it('should return paginated users with teams', async () => {
userRepositoryMock.getAllWithPagination.mockResolvedValue(users);
getUserServiceMock.countUsers.mockResolvedValue(users.length);
getTeamUserServiceMock.getUsersOnlyWithTeams.mockResolvedValue(usersWithTeams);

await expect(getAllUsersWithTeams.execute(getAllUsersWithTeamsProps)).resolves.toEqual(
getAllUsersWithTeamsResult
);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { GetAllUsersWithTeamsPresenter } from '../presenter/get-all-users-with-teams.presenter';
import { GetTeamUserServiceInterface } from 'src/modules/teamUsers/interfaces/services/get.team.user.service.interface';
import { Inject, Injectable } from '@nestjs/common';
import { GetAllUsersWithTeamsUseCaseInterface } from '../interfaces/applications/get-all-users-with-teams.use-case.interface';
import { UserWithTeams } from '../interfaces/type-user-with-teams';
import { TYPES } from '../interfaces/types';
import { UserRepositoryInterface } from '../repository/user.repository.interface';
import { sortAlphabetically } from '../utils/sortings';
import { sortTeamUserListAlphabetically } from '../utils/sortings';
import { GetUserServiceInterface } from '../interfaces/services/get.user.service.interface';
import GetAllUsersWithTeamsUseCaseDto from '../dto/useCase/get-all-users-with-teams.use-case.dto';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { GET_TEAM_USER_SERVICE } from 'src/modules/teamUsers/constants';

@Injectable()
export default class GetAllUsersWithTeamsUseCase implements GetAllUsersWithTeamsUseCaseInterface {
export default class GetAllUsersWithTeamsUseCase
implements UseCase<GetAllUsersWithTeamsUseCaseDto, GetAllUsersWithTeamsPresenter>
{
constructor(
@Inject(TYPES.repository)
private readonly userRepository: UserRepositoryInterface,
Expand All @@ -19,7 +23,10 @@ export default class GetAllUsersWithTeamsUseCase implements GetAllUsersWithTeams
private readonly getTeamUserService: GetTeamUserServiceInterface
) {}

async execute(page = 0, size = 15, searchUser?: string) {
async execute({ page, size, searchUser }: GetAllUsersWithTeamsUseCaseDto) {
page = page ?? 0;
size = size ?? 15;

const [users, count] = await Promise.all([
this.getAllUsersWithPagination(page, size, searchUser),
this.getUserService.countUsers()
Expand Down Expand Up @@ -48,7 +55,7 @@ export default class GetAllUsersWithTeamsUseCase implements GetAllUsersWithTeams
page
};

results.userWithTeams.sort((a, b) => sortAlphabetically(a.user, b.user));
results.userWithTeams = sortTeamUserListAlphabetically(results.userWithTeams);

return results;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createMock } from '@golevelup/ts-jest';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { UserRepositoryInterface } from '../repository/user.repository.interface';
import { getAllUsersUseCase } from '../users.providers';
import * as Users from 'src/modules/users/interfaces/types';
import { Test, TestingModule } from '@nestjs/testing';
import User from '../entities/user.schema';

describe('GetAllUsersUseCase', () => {
let getAllUsers: UseCase<void, User[]>;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
getAllUsersUseCase,
{
provide: Users.TYPES.repository,
useValue: createMock<UserRepositoryInterface>()
}
]
}).compile();

getAllUsers = module.get<UseCase<void, User[]>>(getAllUsersUseCase.provide);
});

beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

it('should be defined', () => {
expect(getAllUsers).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { GetAllUsersUseCaseInterface } from '../interfaces/applications/get-all-users.use-case.interface';
import { TYPES } from '../interfaces/types';
import { UserRepositoryInterface } from '../repository/user.repository.interface';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import User from '../entities/user.schema';

@Injectable()
export default class GetAllUsersUseCase implements GetAllUsersUseCaseInterface {
export default class GetAllUsersUseCase implements UseCase<void, User[]> {
constructor(
@Inject(TYPES.repository)
private readonly userRepository: UserRepositoryInterface
Expand Down
34 changes: 34 additions & 0 deletions backend/src/modules/users/applications/get-user.use-case.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createMock } from '@golevelup/ts-jest';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { getUserUseCase } from '../users.providers';
import * as Users from 'src/modules/users/interfaces/types';
import { Test, TestingModule } from '@nestjs/testing';
import { GetUserServiceInterface } from '../interfaces/services/get.user.service.interface';
import User from '../entities/user.schema';

describe('GetUserUseCase', () => {
let getUser: UseCase<string, User>;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
getUserUseCase,
{
provide: Users.TYPES.services.GetUserService,
useValue: createMock<GetUserServiceInterface>()
}
]
}).compile();

getUser = module.get<UseCase<string, User>>(getUserUseCase.provide);
});

beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});

it('should be defined', () => {
expect(getUser).toBeDefined();
});
});
Loading