upgrade repositories
This commit is contained in:
parent
f111563137
commit
0ccaafc5e0
|
@ -5,6 +5,10 @@ SERVICE_PORT=5002
|
||||||
# PRISMA
|
# PRISMA
|
||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@localhost:5432/mobicoop-test?schema=auth"
|
DATABASE_URL="postgresql://mobicoop:mobicoop@localhost:5432/mobicoop-test?schema=auth"
|
||||||
|
|
||||||
|
# MESSAGE BROKER
|
||||||
|
MESSAGE_BROKER_URI=amqp://v3-broker:5672
|
||||||
|
MESSAGE_BROKER_EXCHANGE=mobicoop
|
||||||
|
|
||||||
# OPA
|
# OPA
|
||||||
OPA_IMAGE=openpolicyagent/opa:0.54.0
|
OPA_IMAGE=openpolicyagent/opa:0.54.0
|
||||||
OPA_URL=http://v3-auth-opa:8181/v1/data/
|
OPA_URL=http://v3-auth-opa:8181/v1/data/
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"test": "npm run migrate:test && dotenv -e .env.test jest",
|
"test": "npm run migrate:test && dotenv -e .env.test jest",
|
||||||
"test:unit": "jest --testPathPattern 'tests/unit/' --verbose",
|
"test:unit": "jest --testPathPattern 'tests/unit/' --verbose",
|
||||||
"test:unit:ci": "jest --testPathPattern 'tests/unit/' --coverage",
|
"test:unit:ci": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||||
"test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose",
|
"test:integration": "npm run migrate:test && dotenv -e .env.test -- jest --testPathPattern 'tests/integration/' --verbose --runInBand",
|
||||||
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/'",
|
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/'",
|
||||||
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject, UnauthorizedException } from '@nestjs/common';
|
||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { DeleteUsernameCommand } from './delete-username.command';
|
import { DeleteUsernameCommand } from './delete-username.command';
|
||||||
import { USERNAME_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
|
import { USERNAME_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
|
||||||
|
@ -13,11 +13,20 @@ export class DeleteUsernameService implements ICommandHandler {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: DeleteUsernameCommand): Promise<boolean> {
|
async execute(command: DeleteUsernameCommand): Promise<boolean> {
|
||||||
const username: UsernameEntity = await this.usernameRepository.findOneById(
|
const username: UsernameEntity = await this.usernameRepository.findByName(
|
||||||
command.name,
|
command.name,
|
||||||
);
|
);
|
||||||
|
const usernamesCount: number = await this.usernameRepository.countUsernames(
|
||||||
|
username.getProps().userId,
|
||||||
|
);
|
||||||
|
if (usernamesCount <= 1)
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
'Authentication must have at least one username',
|
||||||
|
);
|
||||||
username.delete();
|
username.delete();
|
||||||
const isDeleted: boolean = await this.usernameRepository.delete(username);
|
const isDeleted: boolean = await this.usernameRepository.deleteUsername(
|
||||||
|
username,
|
||||||
|
);
|
||||||
return isDeleted;
|
return isDeleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,6 @@ export type UsernameRepositoryPort = RepositoryPort<UsernameEntity> & {
|
||||||
findByType(userId: string, type: Type): Promise<UsernameEntity>;
|
findByType(userId: string, type: Type): Promise<UsernameEntity>;
|
||||||
findByName(name: string): Promise<UsernameEntity>;
|
findByName(name: string): Promise<UsernameEntity>;
|
||||||
updateUsername(oldName: string, entity: UsernameEntity): Promise<void>;
|
updateUsername(oldName: string, entity: UsernameEntity): Promise<void>;
|
||||||
|
deleteUsername(entity: UsernameEntity): Promise<boolean>;
|
||||||
|
countUsernames(userId: string): Promise<number>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,11 +71,13 @@ export class UsernameRepository
|
||||||
updateUsername = async (
|
updateUsername = async (
|
||||||
oldName: string,
|
oldName: string,
|
||||||
entity: UsernameEntity,
|
entity: UsernameEntity,
|
||||||
): Promise<void> =>
|
): Promise<void> => this.update(oldName, entity, 'username');
|
||||||
this.updateWhere(
|
|
||||||
{
|
deleteUsername = async (entity: UsernameEntity): Promise<boolean> =>
|
||||||
username: oldName,
|
this.delete(entity, 'username');
|
||||||
},
|
|
||||||
entity,
|
countUsernames = async (userId: string): Promise<number> =>
|
||||||
);
|
this.count({
|
||||||
|
authUuid: userId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
RpcExceptionCode,
|
RpcExceptionCode,
|
||||||
RpcValidationPipe,
|
RpcValidationPipe,
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { Controller, UsePipes } from '@nestjs/common';
|
import { Controller, UnauthorizedException, UsePipes } from '@nestjs/common';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
import { DeleteUsernameRequestDto } from './dtos/delete-username.request.dto';
|
import { DeleteUsernameRequestDto } from './dtos/delete-username.request.dto';
|
||||||
|
@ -25,12 +25,16 @@ export class DeleteUsernameGrpcController {
|
||||||
try {
|
try {
|
||||||
await this.commandBus.execute(new DeleteUsernameCommand(data));
|
await this.commandBus.execute(new DeleteUsernameCommand(data));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
if (error instanceof UnauthorizedException)
|
||||||
|
throw new RpcException({
|
||||||
|
code: RpcExceptionCode.PERMISSION_DENIED,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
if (error instanceof NotFoundException)
|
if (error instanceof NotFoundException)
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: RpcExceptionCode.NOT_FOUND,
|
code: RpcExceptionCode.NOT_FOUND,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error instanceof DatabaseErrorException)
|
if (error instanceof DatabaseErrorException)
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: RpcExceptionCode.INTERNAL,
|
code: RpcExceptionCode.INTERNAL,
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
import { TestingModule, Test } from '@nestjs/testing';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import { PrismaService } from '@modules/authentication/infrastructure/prisma.service';
|
||||||
|
import { AuthenticationRepository } from '@modules/authentication/infrastructure/authentication.repository';
|
||||||
|
import { AuthenticationMapper } from '@modules/authentication/authentication.mapper';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
||||||
|
import {
|
||||||
|
DatabaseErrorException,
|
||||||
|
NotFoundException,
|
||||||
|
UniqueConstraintException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
|
||||||
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
|
import { CreateAuthenticationProps } from '@modules/authentication/core/domain/authentication.types';
|
||||||
|
|
||||||
|
const uuid = '165192d4-398a-4469-a16b-98c02cc6f531';
|
||||||
|
|
||||||
|
const createAuthenticationProps: CreateAuthenticationProps = {
|
||||||
|
userId: uuid,
|
||||||
|
password: 'somePassword',
|
||||||
|
usernames: [
|
||||||
|
{
|
||||||
|
type: Type.EMAIL,
|
||||||
|
name: 'john.doe@email.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockMessagePublisher = {
|
||||||
|
publish: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLogger = {
|
||||||
|
log: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('AuthenticationRepository', () => {
|
||||||
|
let prismaService: PrismaService;
|
||||||
|
let authenticationRepository: AuthenticationRepository;
|
||||||
|
|
||||||
|
// const createAuthentications = async (nbToCreate = 10) => {
|
||||||
|
// for (let i = 0; i < nbToCreate; i++) {
|
||||||
|
// await prismaService.auth.create({
|
||||||
|
// data: {
|
||||||
|
// uuid: v4(),
|
||||||
|
// password: bcrypt.hashSync(`password-${i}`, 10),
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [EventEmitterModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
AuthenticationRepository,
|
||||||
|
PrismaService,
|
||||||
|
AuthenticationMapper,
|
||||||
|
{
|
||||||
|
provide: MESSAGE_PUBLISHER,
|
||||||
|
useValue: mockMessagePublisher,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
// disable logging
|
||||||
|
.setLogger(mockLogger)
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
prismaService = module.get<PrismaService>(PrismaService);
|
||||||
|
authenticationRepository = module.get<AuthenticationRepository>(
|
||||||
|
AuthenticationRepository,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await prismaService.$disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await prismaService.auth.deleteMany();
|
||||||
|
});
|
||||||
|
|
||||||
|
// describe('findAll', () => {
|
||||||
|
// it('should return an empty data array', async () => {
|
||||||
|
// const res = await authenticationRepository.findAll();
|
||||||
|
// expect(res).toEqual({
|
||||||
|
// data: [],
|
||||||
|
// total: 0,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should return a data array with 8 auths', async () => {
|
||||||
|
// await createAuthentications(8);
|
||||||
|
// const auths = await authenticationRepository.findAll();
|
||||||
|
// expect(auths.data.length).toBe(8);
|
||||||
|
// expect(auths.total).toBe(8);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should return a data array limited to 10 authentications', async () => {
|
||||||
|
// await createAuthentications(20);
|
||||||
|
// const auths = await authenticationRepository.findAll();
|
||||||
|
// expect(auths.data.length).toBe(10);
|
||||||
|
// expect(auths.total).toBe(20);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
describe('findOneById', () => {
|
||||||
|
it('should return an authentication', async () => {
|
||||||
|
const authToFind = await prismaService.auth.create({
|
||||||
|
data: {
|
||||||
|
uuid: v4(),
|
||||||
|
password: bcrypt.hashSync(`password`, 10),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const auth = await authenticationRepository.findOneById(authToFind.uuid);
|
||||||
|
expect(auth.id).toBe(authToFind.uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if record is not found', async () => {
|
||||||
|
await expect(
|
||||||
|
authenticationRepository.findOneById(
|
||||||
|
'544572be-11fb-4244-8235-587221fc9104',
|
||||||
|
),
|
||||||
|
).rejects.toBeInstanceOf(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create an authentication', async () => {
|
||||||
|
const beforeCount = await prismaService.auth.count();
|
||||||
|
|
||||||
|
const authenticationToCreate: AuthenticationEntity =
|
||||||
|
await AuthenticationEntity.create(createAuthenticationProps);
|
||||||
|
await authenticationRepository.insert(authenticationToCreate);
|
||||||
|
|
||||||
|
const afterCount = await prismaService.auth.count();
|
||||||
|
|
||||||
|
expect(afterCount - beforeCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a UniqueConstraintException if authentication already exists', async () => {
|
||||||
|
await prismaService.auth.create({
|
||||||
|
data: {
|
||||||
|
uuid: uuid,
|
||||||
|
password: bcrypt.hashSync(`password`, 10),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const authenticationToCreate: AuthenticationEntity =
|
||||||
|
await AuthenticationEntity.create(createAuthenticationProps);
|
||||||
|
await expect(
|
||||||
|
authenticationRepository.insert(authenticationToCreate),
|
||||||
|
).rejects.toBeInstanceOf(UniqueConstraintException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should update an authentication', async () => {
|
||||||
|
const authenticationToUpdate = await prismaService.auth.create({
|
||||||
|
data: {
|
||||||
|
uuid: v4(),
|
||||||
|
password: bcrypt.hashSync(`password`, 10),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const toUpdate: AuthenticationEntity = await AuthenticationEntity.create(
|
||||||
|
createAuthenticationProps,
|
||||||
|
);
|
||||||
|
await authenticationRepository.update(
|
||||||
|
authenticationToUpdate.uuid,
|
||||||
|
toUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedAuthentication = await prismaService.auth.findUnique({
|
||||||
|
where: {
|
||||||
|
uuid: toUpdate.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedAuthentication.uuid).toBe(uuid);
|
||||||
|
expect(authenticationToUpdate.updatedAt.getTime()).toBeLessThan(
|
||||||
|
updatedAuthentication.updatedAt.getTime(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a DatabaseException if id is unknown', async () => {
|
||||||
|
const toUpdate: AuthenticationEntity = await AuthenticationEntity.create(
|
||||||
|
createAuthenticationProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
authenticationRepository.update(
|
||||||
|
'544572be-11fb-4244-8235-587221fc9104',
|
||||||
|
toUpdate,
|
||||||
|
),
|
||||||
|
).rejects.toBeInstanceOf(DatabaseErrorException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should delete an authentication', async () => {
|
||||||
|
await prismaService.auth.create({
|
||||||
|
data: {
|
||||||
|
uuid,
|
||||||
|
password: bcrypt.hashSync(`password`, 10),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const toDelete: AuthenticationEntity = await AuthenticationEntity.create(
|
||||||
|
createAuthenticationProps,
|
||||||
|
);
|
||||||
|
await authenticationRepository.delete(toDelete);
|
||||||
|
|
||||||
|
const count = await prismaService.auth.count();
|
||||||
|
expect(count).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a DatabaseException if authentication does not exist', async () => {
|
||||||
|
const toDelete: AuthenticationEntity = await AuthenticationEntity.create(
|
||||||
|
createAuthenticationProps,
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
authenticationRepository.delete(toDelete),
|
||||||
|
).rejects.toBeInstanceOf(DatabaseErrorException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,202 @@
|
||||||
|
import { TestingModule, Test } from '@nestjs/testing';
|
||||||
|
import { PrismaService } from '@modules/authentication/infrastructure/prisma.service';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
||||||
|
import {
|
||||||
|
DatabaseErrorException,
|
||||||
|
NotFoundException,
|
||||||
|
UniqueConstraintException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import {
|
||||||
|
CreateUsernameProps,
|
||||||
|
Type,
|
||||||
|
} from '@modules/authentication/core/domain/username.types';
|
||||||
|
import { UsernameRepository } from '@modules/authentication/infrastructure/username.repository';
|
||||||
|
import { UsernameMapper } from '@modules/authentication/username.mapper';
|
||||||
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import { UsernameEntity } from '@modules/authentication/core/domain/username.entity';
|
||||||
|
|
||||||
|
const authUuid = 'a4524d22-7be3-46cd-8444-3145470476dc';
|
||||||
|
|
||||||
|
const createUsernameProps: CreateUsernameProps = {
|
||||||
|
userId: authUuid,
|
||||||
|
type: Type.EMAIL,
|
||||||
|
name: 'john.doe@email.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockMessagePublisher = {
|
||||||
|
publish: jest.fn().mockImplementation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLogger = {
|
||||||
|
log: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('UsernameRepository', () => {
|
||||||
|
let prismaService: PrismaService;
|
||||||
|
let usernameRepository: UsernameRepository;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [EventEmitterModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
UsernameRepository,
|
||||||
|
PrismaService,
|
||||||
|
UsernameMapper,
|
||||||
|
{
|
||||||
|
provide: MESSAGE_PUBLISHER,
|
||||||
|
useValue: mockMessagePublisher,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
// disable logging
|
||||||
|
.setLogger(mockLogger)
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
prismaService = module.get<PrismaService>(PrismaService);
|
||||||
|
usernameRepository = module.get<UsernameRepository>(UsernameRepository);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await prismaService.$disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await prismaService.username.deleteMany();
|
||||||
|
await prismaService.auth.deleteMany();
|
||||||
|
await prismaService.auth.create({
|
||||||
|
data: {
|
||||||
|
uuid: authUuid,
|
||||||
|
password: bcrypt.hashSync(`password`, 10),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOne', () => {
|
||||||
|
it('should return a Username', async () => {
|
||||||
|
await prismaService.username.create({
|
||||||
|
data: {
|
||||||
|
authUuid,
|
||||||
|
username: 'john.doe@email.com',
|
||||||
|
type: Type.EMAIL,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const username = await usernameRepository.findOne({
|
||||||
|
username: 'john.doe@email.com',
|
||||||
|
});
|
||||||
|
expect(username.id).toBe('john.doe@email.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if record is not found', async () => {
|
||||||
|
await expect(
|
||||||
|
usernameRepository.findOne({
|
||||||
|
username: 'jane.doe@email.com',
|
||||||
|
}),
|
||||||
|
).rejects.toBeInstanceOf(NotFoundException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a username', async () => {
|
||||||
|
const beforeCount = await prismaService.username.count();
|
||||||
|
|
||||||
|
const usernameToCreate: UsernameEntity = await UsernameEntity.create(
|
||||||
|
createUsernameProps,
|
||||||
|
);
|
||||||
|
await usernameRepository.insert(usernameToCreate);
|
||||||
|
|
||||||
|
const afterCount = await prismaService.username.count();
|
||||||
|
|
||||||
|
expect(afterCount - beforeCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a UniqueConstraintException if username already exists', async () => {
|
||||||
|
await prismaService.username.create({
|
||||||
|
data: {
|
||||||
|
authUuid,
|
||||||
|
type: Type.EMAIL,
|
||||||
|
username: 'john.doe@email.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const usernameToCreate: UsernameEntity = await UsernameEntity.create(
|
||||||
|
createUsernameProps,
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
usernameRepository.insert(usernameToCreate),
|
||||||
|
).rejects.toBeInstanceOf(UniqueConstraintException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update username', () => {
|
||||||
|
it('should update the name of a username', async () => {
|
||||||
|
const usernameToUpdate = await prismaService.username.create({
|
||||||
|
data: {
|
||||||
|
authUuid,
|
||||||
|
type: Type.EMAIL,
|
||||||
|
username: 'johnny.doe@email.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const toUpdate: UsernameEntity = await UsernameEntity.create(
|
||||||
|
createUsernameProps,
|
||||||
|
);
|
||||||
|
await usernameRepository.updateUsername(
|
||||||
|
usernameToUpdate.username,
|
||||||
|
toUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedUsername = await prismaService.username.findUnique({
|
||||||
|
where: {
|
||||||
|
username: toUpdate.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedUsername.username).toBe('john.doe@email.com');
|
||||||
|
expect(usernameToUpdate.updatedAt.getTime()).toBeLessThan(
|
||||||
|
updatedUsername.updatedAt.getTime(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a DatabaseException if id is unknown', async () => {
|
||||||
|
const toUpdate: UsernameEntity = await UsernameEntity.create(
|
||||||
|
createUsernameProps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
usernameRepository.updateUsername('jane.doe@email.com', toUpdate),
|
||||||
|
).rejects.toBeInstanceOf(DatabaseErrorException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should delete a username', async () => {
|
||||||
|
await prismaService.username.create({
|
||||||
|
data: {
|
||||||
|
authUuid,
|
||||||
|
type: Type.EMAIL,
|
||||||
|
username: 'john.doe@email.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const toDelete: UsernameEntity = await UsernameEntity.create(
|
||||||
|
createUsernameProps,
|
||||||
|
);
|
||||||
|
await usernameRepository.delete(toDelete);
|
||||||
|
|
||||||
|
const count = await prismaService.username.count();
|
||||||
|
expect(count).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a DatabaseException if username does not exist', async () => {
|
||||||
|
const toDelete: UsernameEntity = await UsernameEntity.create(
|
||||||
|
createUsernameProps,
|
||||||
|
);
|
||||||
|
await expect(usernameRepository.delete(toDelete)).rejects.toBeInstanceOf(
|
||||||
|
DatabaseErrorException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,6 +2,7 @@ import { USERNAME_REPOSITORY } from '@modules/authentication/authentication.di-t
|
||||||
import { DeleteUsernameCommand } from '@modules/authentication/core/application/commands/delete-username/delete-username.command';
|
import { DeleteUsernameCommand } from '@modules/authentication/core/application/commands/delete-username/delete-username.command';
|
||||||
import { DeleteUsernameService } from '@modules/authentication/core/application/commands/delete-username/delete-username.service';
|
import { DeleteUsernameService } from '@modules/authentication/core/application/commands/delete-username/delete-username.service';
|
||||||
import { DeleteUsernameRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/delete-username.request.dto';
|
import { DeleteUsernameRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/delete-username.request.dto';
|
||||||
|
import { UnauthorizedException } from '@nestjs/common';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
const deleteUsernameRequest: DeleteUsernameRequestDto = {
|
const deleteUsernameRequest: DeleteUsernameRequestDto = {
|
||||||
|
@ -10,11 +11,18 @@ const deleteUsernameRequest: DeleteUsernameRequestDto = {
|
||||||
|
|
||||||
const mockUsernameEntity = {
|
const mockUsernameEntity = {
|
||||||
delete: jest.fn(),
|
delete: jest.fn(),
|
||||||
|
getProps: jest.fn().mockImplementation(() => ({
|
||||||
|
userId: 'b1643072-757f-4e3b-87f2-63af3684afb8',
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockUsernameRepository = {
|
const mockUsernameRepository = {
|
||||||
findOneById: jest.fn().mockImplementation(() => mockUsernameEntity),
|
findByName: jest.fn().mockImplementation(() => mockUsernameEntity),
|
||||||
delete: jest.fn().mockImplementationOnce(() => true),
|
countUsernames: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => 2)
|
||||||
|
.mockImplementationOnce(() => 1),
|
||||||
|
deleteUsername: jest.fn().mockImplementationOnce(() => true),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Delete Username Service', () => {
|
describe('Delete Username Service', () => {
|
||||||
|
@ -50,5 +58,10 @@ describe('Delete Username Service', () => {
|
||||||
);
|
);
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
it('should throw an exception when trying to delete the last username', async () => {
|
||||||
|
await expect(
|
||||||
|
deleteUsernameService.execute(deleteUsernameCommand),
|
||||||
|
).rejects.toBeInstanceOf(UnauthorizedException);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,8 +22,9 @@ const mockPrismaService = {
|
||||||
};
|
};
|
||||||
return record;
|
return record;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
update: jest.fn().mockImplementation(),
|
update: jest.fn().mockImplementation(),
|
||||||
|
delete: jest.fn().mockImplementation(),
|
||||||
|
count: jest.fn().mockImplementation(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ describe('Username repository', () => {
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(usernameRepository).toBeDefined();
|
expect(usernameRepository).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find a username by its userId and Type', async () => {
|
it('should find a username by its userId and Type', async () => {
|
||||||
jest.spyOn(usernameRepository, 'findOne');
|
jest.spyOn(usernameRepository, 'findOne');
|
||||||
const username: UsernameEntity = await usernameRepository.findByType(
|
const username: UsernameEntity = await usernameRepository.findByType(
|
||||||
|
@ -80,6 +82,7 @@ describe('Username repository', () => {
|
||||||
});
|
});
|
||||||
expect(username.getProps().name).toBe('john.doe@email.com');
|
expect(username.getProps().name).toBe('john.doe@email.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find a username by its name', async () => {
|
it('should find a username by its name', async () => {
|
||||||
jest.spyOn(usernameRepository, 'findOne');
|
jest.spyOn(usernameRepository, 'findOne');
|
||||||
const username: UsernameEntity = await usernameRepository.findByName(
|
const username: UsernameEntity = await usernameRepository.findByName(
|
||||||
|
@ -91,8 +94,9 @@ describe('Username repository', () => {
|
||||||
});
|
});
|
||||||
expect(username.getProps().name).toBe('john.doe@email.com');
|
expect(username.getProps().name).toBe('john.doe@email.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update a username', async () => {
|
it('should update a username', async () => {
|
||||||
jest.spyOn(usernameRepository, 'updateWhere');
|
jest.spyOn(usernameRepository, 'update');
|
||||||
const usernameToUpdate: UsernameEntity = await UsernameEntity.create({
|
const usernameToUpdate: UsernameEntity = await UsernameEntity.create({
|
||||||
userId: '165192d4-398a-4469-a16b-98c02cc6f531',
|
userId: '165192d4-398a-4469-a16b-98c02cc6f531',
|
||||||
type: Type.EMAIL,
|
type: Type.EMAIL,
|
||||||
|
@ -102,12 +106,35 @@ describe('Username repository', () => {
|
||||||
'john.doe@email.com',
|
'john.doe@email.com',
|
||||||
usernameToUpdate,
|
usernameToUpdate,
|
||||||
);
|
);
|
||||||
expect(usernameRepository.updateWhere).toHaveBeenCalledTimes(1);
|
expect(usernameRepository.update).toHaveBeenCalledTimes(1);
|
||||||
expect(usernameRepository.updateWhere).toHaveBeenCalledWith(
|
expect(usernameRepository.update).toHaveBeenCalledWith(
|
||||||
{
|
'john.doe@email.com',
|
||||||
username: 'john.doe@email.com',
|
|
||||||
},
|
|
||||||
usernameToUpdate,
|
usernameToUpdate,
|
||||||
|
'username',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should delete a username', async () => {
|
||||||
|
jest.spyOn(usernameRepository, 'delete');
|
||||||
|
const usernameToDelete: UsernameEntity = await UsernameEntity.create({
|
||||||
|
userId: '165192d4-398a-4469-a16b-98c02cc6f531',
|
||||||
|
type: Type.EMAIL,
|
||||||
|
name: 'john.doe@new-email.com',
|
||||||
|
});
|
||||||
|
await usernameRepository.deleteUsername(usernameToDelete);
|
||||||
|
expect(usernameRepository.delete).toHaveBeenCalledTimes(1);
|
||||||
|
expect(usernameRepository.delete).toHaveBeenCalledWith(
|
||||||
|
usernameToDelete,
|
||||||
|
'username',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should count usernames for a given userId', async () => {
|
||||||
|
jest.spyOn(usernameRepository, 'count');
|
||||||
|
await usernameRepository.countUsernames('john.doe@email.com');
|
||||||
|
expect(usernameRepository.count).toHaveBeenCalledTimes(1);
|
||||||
|
expect(usernameRepository.count).toHaveBeenCalledWith({
|
||||||
|
authUuid: 'john.doe@email.com',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
import { DeleteUsernameGrpcController } from '@modules/authentication/interface/grpc-controllers/delete-username.grpc.controller';
|
import { DeleteUsernameGrpcController } from '@modules/authentication/interface/grpc-controllers/delete-username.grpc.controller';
|
||||||
import { DeleteUsernameRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/delete-username.request.dto';
|
import { DeleteUsernameRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/delete-username.request.dto';
|
||||||
|
import { UnauthorizedException } from '@nestjs/common';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { RpcException } from '@nestjs/microservices';
|
import { RpcException } from '@nestjs/microservices';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
@ -17,6 +18,9 @@ const mockCommandBus = {
|
||||||
execute: jest
|
execute: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementationOnce(() => ({}))
|
.mockImplementationOnce(() => ({}))
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
})
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
})
|
})
|
||||||
|
@ -61,6 +65,18 @@ describe('Delete Username Grpc Controller', () => {
|
||||||
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw a dedicated RpcException when trying to delete the last username', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await deleteUsernameGrpcController.delete(deleteUsernameRequest);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.PERMISSION_DENIED);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw a dedicated RpcException if username does not exist', async () => {
|
it('should throw a dedicated RpcException if username does not exist', async () => {
|
||||||
jest.spyOn(mockCommandBus, 'execute');
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
|
|
Loading…
Reference in New Issue