Merge branch 'logging' into 'main'

Logging

See merge request mobicoop/lab/v3/services/auth!3
This commit is contained in:
Gsk54 2022-12-23 14:51:42 +00:00
commit 5d1f92cb9e
23 changed files with 309 additions and 40 deletions

2
.env
View File

@ -7,8 +7,6 @@ SERVICE_PORT=5002
DATABASE_URL="postgresql://auth:auth@v3-auth-db:5432/auth?schema=public" DATABASE_URL="postgresql://auth:auth@v3-auth-db:5432/auth?schema=public"
# RABBIT MQ # RABBIT MQ
RMQ_EXCHANGE_NAME=user
RMQ_EXCHANGE_TYPE=topic
RMQ_URI=amqp://v3-gateway-broker:5672 RMQ_URI=amqp://v3-gateway-broker:5672
# POSTGRES # POSTGRES

View File

@ -7,8 +7,6 @@ SERVICE_PORT=5002
DATABASE_URL="postgresql://auth:auth@db:5432/auth?schema=public" DATABASE_URL="postgresql://auth:auth@db:5432/auth?schema=public"
# RABBIT MQ # RABBIT MQ
RMQ_EXCHANGES=user
RMQ_EXCHANGE_TYPE=topic
RMQ_URI=amqp://localhost:5672 RMQ_URI=amqp://localhost:5672
# POSTGRES # POSTGRES

View File

@ -13,26 +13,27 @@ export class AuthMessagerController {
@RabbitSubscribe({ @RabbitSubscribe({
exchange: 'user', exchange: 'user',
routingKey: 'user.update', routingKey: 'update',
queue: 'auth-user-update',
}) })
public async userUpdatedHandler(message: string) { public async userUpdatedHandler(message: string) {
const updatedUser = JSON.parse(message); const updatedUser = JSON.parse(message);
if (!updatedUser.hasOwnProperty('uuid')) throw new Error(); if (!updatedUser.hasOwnProperty('uuid')) throw new Error();
if (updatedUser.hasOwnProperty('email')) { if (updatedUser.hasOwnProperty('email') && updatedUser.email) {
const updateUsernameRequest = new UpdateUsernameRequest(); const updateUsernameRequest = new UpdateUsernameRequest();
updateUsernameRequest.uuid = updatedUser.uuid; updateUsernameRequest.uuid = updatedUser.uuid;
updateUsernameRequest.username = updatedUser.email; updateUsernameRequest.username = updatedUser.email;
updateUsernameRequest.type = Type.EMAIL; updateUsernameRequest.type = Type.EMAIL;
this._commandBus.execute( await this._commandBus.execute(
new UpdateUsernameCommand(updateUsernameRequest), new UpdateUsernameCommand(updateUsernameRequest),
); );
} }
if (updatedUser.hasOwnProperty('phone')) { if (updatedUser.hasOwnProperty('phone') && updatedUser.phone) {
const updateUsernameRequest = new UpdateUsernameRequest(); const updateUsernameRequest = new UpdateUsernameRequest();
updateUsernameRequest.uuid = updatedUser.uuid; updateUsernameRequest.uuid = updatedUser.uuid;
updateUsernameRequest.username = updatedUser.phone; updateUsernameRequest.username = updatedUser.phone;
updateUsernameRequest.type = Type.PHONE; updateUsernameRequest.type = Type.PHONE;
this._commandBus.execute( await this._commandBus.execute(
new UpdateUsernameCommand(updateUsernameRequest), new UpdateUsernameCommand(updateUsernameRequest),
); );
} }
@ -40,13 +41,14 @@ export class AuthMessagerController {
@RabbitSubscribe({ @RabbitSubscribe({
exchange: 'user', exchange: 'user',
routingKey: 'user.delete', routingKey: 'delete',
queue: 'auth-user-delete',
}) })
public async userDeletedHandler(message: string) { public async userDeletedHandler(message: string) {
const deletedUser = JSON.parse(message); const deletedUser = JSON.parse(message);
if (!deletedUser.hasOwnProperty('uuid')) throw new Error(); if (!deletedUser.hasOwnProperty('uuid')) throw new Error();
const deleteAuthRequest = new DeleteAuthRequest(); const deleteAuthRequest = new DeleteAuthRequest();
deleteAuthRequest.uuid = deletedUser.uuid; deleteAuthRequest.uuid = deletedUser.uuid;
this._commandBus.execute(new DeleteAuthCommand(deleteAuthRequest)); await this._commandBus.execute(new DeleteAuthCommand(deleteAuthRequest));
} }
} }

View File

@ -1,6 +1,6 @@
import { Mapper } from '@automapper/core'; import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs'; import { InjectMapper } from '@automapper/nestjs';
import { Controller } from '@nestjs/common'; import { Controller, UsePipes } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices'; import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { DatabaseException } from 'src/modules/database/src/exceptions/DatabaseException'; import { DatabaseException } from 'src/modules/database/src/exceptions/DatabaseException';
@ -21,8 +21,15 @@ import { Auth } from '../../domain/entities/auth';
import { Username } from '../../domain/entities/username'; import { Username } from '../../domain/entities/username';
import { ValidateAuthQuery } from '../../queries/validate-auth.query'; import { ValidateAuthQuery } from '../../queries/validate-auth.query';
import { AuthPresenter } from './auth.presenter'; import { AuthPresenter } from './auth.presenter';
import { RpcValidationPipe } from './rpc.validation-pipe';
import { UsernamePresenter } from './username.presenter'; import { UsernamePresenter } from './username.presenter';
@UsePipes(
new RpcValidationPipe({
whitelist: true,
forbidUnknownValues: false,
}),
)
@Controller() @Controller()
export class AuthController { export class AuthController {
constructor( constructor(

View File

@ -0,0 +1,14 @@
import { Injectable, ValidationPipe } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
@Injectable()
export class RpcValidationPipe extends ValidationPipe {
createExceptionFactory() {
return (validationErrors = []) => {
return new RpcException({
code: 3,
message: this.flattenValidationErrors(validationErrors),
});
};
}
}

View File

@ -0,0 +1,14 @@
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Injectable } from '@nestjs/common';
import { IMessageBroker } from '../../domain/interfaces/message-broker';
@Injectable()
export class AuthMessager extends IMessageBroker {
constructor(private readonly _amqpConnection: AmqpConnection) {
super('auth');
}
publish(routingKey: string, message: string): void {
this._amqpConnection.publish(this.exchange, routingKey, message);
}
}

View File

@ -0,0 +1,14 @@
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Injectable } from '@nestjs/common';
import { IMessageBroker } from '../../domain/interfaces/message-broker';
@Injectable()
export class LoggingMessager extends IMessageBroker {
constructor(private readonly _amqpConnection: AmqpConnection) {
super('logging');
}
publish(routingKey: string, message: string): void {
this._amqpConnection.publish(this.exchange, routingKey, message);
}
}

View File

@ -25,8 +25,12 @@ import { AuthMessagerController } from './adapters/primaries/auth-messager.contr
useFactory: async (configService: ConfigService) => ({ useFactory: async (configService: ConfigService) => ({
exchanges: [ exchanges: [
{ {
name: configService.get<string>('RMQ_EXCHANGE_NAME'), name: 'user',
type: configService.get<string>('RMQ_EXCHANGE_TYPE'), type: 'topic',
},
{
name: 'logging',
type: 'topic',
}, },
], ],
uri: configService.get<string>('RMQ_URI'), uri: configService.get<string>('RMQ_URI'),

View File

@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export abstract class IMessageBroker {
exchange: string;
constructor(exchange: string) {
this.exchange = exchange;
}
abstract publish(routingKey: string, message: string): void;
}

View File

@ -1,11 +1,15 @@
import { CommandHandler } from '@nestjs/cqrs'; import { CommandHandler } from '@nestjs/cqrs';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { AddUsernameCommand } from '../../commands/add-username.command'; import { AddUsernameCommand } from '../../commands/add-username.command';
import { Username } from '../entities/username'; import { Username } from '../entities/username';
@CommandHandler(AddUsernameCommand) @CommandHandler(AddUsernameCommand)
export class AddUsernameUseCase { export class AddUsernameUseCase {
constructor(private readonly _usernameRepository: UsernameRepository) {} constructor(
private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: AddUsernameCommand): Promise<Username> { async execute(command: AddUsernameCommand): Promise<Username> {
const { uuid, username, type } = command.addUsernameRequest; const { uuid, username, type } = command.addUsernameRequest;
@ -15,8 +19,15 @@ export class AddUsernameUseCase {
type, type,
username, username,
}); });
} catch (e) { } catch (error) {
throw e; this._loggingMessager.publish(
'auth.username.add.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
} }
} }
} }

View File

@ -4,12 +4,14 @@ import { CreateAuthCommand } from '../../commands/create-auth.command';
import { Auth } from '../entities/auth'; import { Auth } from '../entities/auth';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
@CommandHandler(CreateAuthCommand) @CommandHandler(CreateAuthCommand)
export class CreateAuthUseCase { export class CreateAuthUseCase {
constructor( constructor(
private readonly _authRepository: AuthRepository, private readonly _authRepository: AuthRepository,
private readonly _usernameRepository: UsernameRepository, private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {} ) {}
async execute(command: CreateAuthCommand): Promise<Auth> { async execute(command: CreateAuthCommand): Promise<Auth> {
@ -28,8 +30,15 @@ export class CreateAuthUseCase {
}); });
return auth; return auth;
} catch (e) { } catch (error) {
throw e; this._loggingMessager.publish(
'auth.create.critical',
JSON.stringify({
command,
error,
}),
);
throw error;
} }
} }
} }

View File

@ -1,5 +1,6 @@
import { CommandHandler } from '@nestjs/cqrs'; import { CommandHandler } from '@nestjs/cqrs';
import { AuthRepository } from '../../adapters/secondaries/auth.repository'; import { AuthRepository } from '../../adapters/secondaries/auth.repository';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { DeleteAuthCommand } from '../../commands/delete-auth.command'; import { DeleteAuthCommand } from '../../commands/delete-auth.command';
@ -8,6 +9,7 @@ export class DeleteAuthUseCase {
constructor( constructor(
private readonly _authRepository: AuthRepository, private readonly _authRepository: AuthRepository,
private readonly _usernameRepository: UsernameRepository, private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {} ) {}
async execute(command: DeleteAuthCommand) { async execute(command: DeleteAuthCommand) {
@ -18,8 +20,15 @@ export class DeleteAuthUseCase {
return await this._authRepository.delete({ return await this._authRepository.delete({
uuid: command.deleteAuthRequest.uuid, uuid: command.deleteAuthRequest.uuid,
}); });
} catch (e) { } catch (error) {
throw e; this._loggingMessager.publish(
'auth.delete.critical',
JSON.stringify({
command,
error,
}),
);
throw error;
} }
} }
} }

View File

@ -1,11 +1,15 @@
import { UnauthorizedException } from '@nestjs/common'; import { UnauthorizedException } from '@nestjs/common';
import { CommandHandler } from '@nestjs/cqrs'; import { CommandHandler } from '@nestjs/cqrs';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { DeleteUsernameCommand } from '../../commands/delete-username.command'; import { DeleteUsernameCommand } from '../../commands/delete-username.command';
@CommandHandler(DeleteUsernameCommand) @CommandHandler(DeleteUsernameCommand)
export class DeleteUsernameUseCase { export class DeleteUsernameUseCase {
constructor(private readonly _usernameRepository: UsernameRepository) {} constructor(
private readonly _usernameRepository: UsernameRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: DeleteUsernameCommand) { async execute(command: DeleteUsernameCommand) {
try { try {
@ -20,8 +24,15 @@ export class DeleteUsernameUseCase {
return await this._usernameRepository.delete({ username }); return await this._usernameRepository.delete({ username });
} }
throw new UnauthorizedException(); throw new UnauthorizedException();
} catch (e) { } catch (error) {
throw e; this._loggingMessager.publish(
'auth.username.delete.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
} }
} }
} }

View File

@ -3,10 +3,14 @@ import { AuthRepository } from '../../adapters/secondaries/auth.repository';
import { Auth } from '../entities/auth'; import { Auth } from '../entities/auth';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { UpdatePasswordCommand } from '../../commands/update-password.command'; import { UpdatePasswordCommand } from '../../commands/update-password.command';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
@CommandHandler(UpdatePasswordCommand) @CommandHandler(UpdatePasswordCommand)
export class UpdatePasswordUseCase { export class UpdatePasswordUseCase {
constructor(private readonly _authRepository: AuthRepository) {} constructor(
private readonly _authRepository: AuthRepository,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: UpdatePasswordCommand): Promise<Auth> { async execute(command: UpdatePasswordCommand): Promise<Auth> {
const { uuid, password } = command.updatePasswordRequest; const { uuid, password } = command.updatePasswordRequest;
@ -16,8 +20,15 @@ export class UpdatePasswordUseCase {
return await this._authRepository.update(uuid, { return await this._authRepository.update(uuid, {
password: hash, password: hash,
}); });
} catch (e) { } catch (error) {
throw e; this._loggingMessager.publish(
'auth.password.update.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
} }
} }
} }

View File

@ -1,25 +1,64 @@
import { CommandHandler } from '@nestjs/cqrs'; import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs';
import { BadRequestException } from '@nestjs/common';
import { CommandBus, CommandHandler } from '@nestjs/cqrs';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { AddUsernameCommand } from '../../commands/add-username.command';
import { UpdateUsernameCommand } from '../../commands/update-username.command'; import { UpdateUsernameCommand } from '../../commands/update-username.command';
import { AddUsernameRequest } from '../dtos/add-username.request';
import { UpdateUsernameRequest } from '../dtos/update-username.request';
import { Username } from '../entities/username'; import { Username } from '../entities/username';
@CommandHandler(UpdateUsernameCommand) @CommandHandler(UpdateUsernameCommand)
export class UpdateUsernameUseCase { export class UpdateUsernameUseCase {
constructor(private readonly _usernameRepository: UsernameRepository) {} constructor(
private readonly _usernameRepository: UsernameRepository,
private readonly _commandBus: CommandBus,
@InjectMapper() private readonly _mapper: Mapper,
private readonly _loggingMessager: LoggingMessager,
) {}
async execute(command: UpdateUsernameCommand): Promise<Username> { async execute(command: UpdateUsernameCommand): Promise<Username> {
const { uuid, username, type } = command.updateUsernameRequest; const { uuid, username, type } = command.updateUsernameRequest;
try { if (!username) throw new BadRequestException();
return await this._usernameRepository.updateWhere( // update username if it exists, otherwise create it
{ const existingUsername = await this._usernameRepository.findOne({
uuid_type: { uuid,
uuid, type,
type, });
if (existingUsername) {
try {
return await this._usernameRepository.updateWhere(
{
uuid_type: {
uuid,
type,
},
}, },
}, {
{ username,
username, },
}, );
} catch (error) {
this._loggingMessager.publish(
'auth.username.update.warning',
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
const addUsernameRequest = this._mapper.map(
command.updateUsernameRequest,
UpdateUsernameRequest,
AddUsernameRequest,
);
try {
return await this._commandBus.execute(
new AddUsernameCommand(addUsernameRequest),
); );
} catch (e) { } catch (e) {
throw e; throw e;

View File

@ -2,6 +2,8 @@ import { createMap, Mapper } from '@automapper/core';
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { UsernamePresenter } from '../adapters/primaries/username.presenter'; import { UsernamePresenter } from '../adapters/primaries/username.presenter';
import { AddUsernameRequest } from '../domain/dtos/add-username.request';
import { UpdateUsernameRequest } from '../domain/dtos/update-username.request';
import { Username } from '../domain/entities/username'; import { Username } from '../domain/entities/username';
@Injectable() @Injectable()
@ -13,6 +15,7 @@ export class UsernameProfile extends AutomapperProfile {
override get profile() { override get profile() {
return (mapper: any) => { return (mapper: any) => {
createMap(mapper, Username, UsernamePresenter); createMap(mapper, Username, UsernamePresenter);
createMap(mapper, UpdateUsernameRequest, AddUsernameRequest);
}; };
} }
} }

View File

@ -8,6 +8,7 @@ import { Type } from '../../domain/dtos/type.enum';
import { AddUsernameRequest } from '../../domain/dtos/add-username.request'; import { AddUsernameRequest } from '../../domain/dtos/add-username.request';
import { AddUsernameCommand } from '../../commands/add-username.command'; import { AddUsernameCommand } from '../../commands/add-username.command';
import { AddUsernameUseCase } from '../../domain/usecases/add-username.usecase'; import { AddUsernameUseCase } from '../../domain/usecases/add-username.usecase';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
const addUsernameRequest: AddUsernameRequest = { const addUsernameRequest: AddUsernameRequest = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
@ -22,6 +23,10 @@ const mockUsernameRepository = {
create: jest.fn().mockResolvedValue(addUsernameRequest), create: jest.fn().mockResolvedValue(addUsernameRequest),
}; };
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
describe('AddUsernameUseCase', () => { describe('AddUsernameUseCase', () => {
let addUsernameUseCase: AddUsernameUseCase; let addUsernameUseCase: AddUsernameUseCase;
@ -33,6 +38,10 @@ describe('AddUsernameUseCase', () => {
provide: UsernameRepository, provide: UsernameRepository,
useValue: mockUsernameRepository, useValue: mockUsernameRepository,
}, },
{
provide: LoggingMessager,
useValue: mockMessager,
},
AddUsernameUseCase, AddUsernameUseCase,
AuthProfile, AuthProfile,
], ],

View File

@ -9,6 +9,7 @@ import { CreateAuthUseCase } from '../../domain/usecases/create-auth.usecase';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { Type } from '../../domain/dtos/type.enum'; import { Type } from '../../domain/dtos/type.enum';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
const newAuthRequest: CreateAuthRequest = { const newAuthRequest: CreateAuthRequest = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
@ -33,6 +34,10 @@ const mockUsernameRepository = {
}), }),
}; };
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
describe('CreateAuthUseCase', () => { describe('CreateAuthUseCase', () => {
let createAuthUseCase: CreateAuthUseCase; let createAuthUseCase: CreateAuthUseCase;
@ -48,6 +53,10 @@ describe('CreateAuthUseCase', () => {
provide: UsernameRepository, provide: UsernameRepository,
useValue: mockUsernameRepository, useValue: mockUsernameRepository,
}, },
{
provide: LoggingMessager,
useValue: mockMessager,
},
CreateAuthUseCase, CreateAuthUseCase,
], ],
}).compile(); }).compile();

View File

@ -2,6 +2,7 @@ import { classes } from '@automapper/classes';
import { AutomapperModule } from '@automapper/nestjs'; import { AutomapperModule } from '@automapper/nestjs';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { AuthRepository } from '../../adapters/secondaries/auth.repository'; import { AuthRepository } from '../../adapters/secondaries/auth.repository';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { DeleteAuthCommand } from '../../commands/delete-auth.command'; import { DeleteAuthCommand } from '../../commands/delete-auth.command';
import { DeleteAuthRequest } from '../../domain/dtos/delete-auth.request'; import { DeleteAuthRequest } from '../../domain/dtos/delete-auth.request';
@ -44,6 +45,10 @@ const mockUsernameRepository = {
deleteMany: jest.fn().mockResolvedValue(undefined), deleteMany: jest.fn().mockResolvedValue(undefined),
}; };
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
describe('DeleteAuthUseCase', () => { describe('DeleteAuthUseCase', () => {
let deleteAuthUseCase: DeleteAuthUseCase; let deleteAuthUseCase: DeleteAuthUseCase;
@ -59,6 +64,10 @@ describe('DeleteAuthUseCase', () => {
provide: UsernameRepository, provide: UsernameRepository,
useValue: mockUsernameRepository, useValue: mockUsernameRepository,
}, },
{
provide: LoggingMessager,
useValue: mockMessager,
},
DeleteAuthUseCase, DeleteAuthUseCase,
], ],
}).compile(); }).compile();

View File

@ -2,6 +2,7 @@ import { classes } from '@automapper/classes';
import { AutomapperModule } from '@automapper/nestjs'; import { AutomapperModule } from '@automapper/nestjs';
import { UnauthorizedException } from '@nestjs/common'; import { UnauthorizedException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { DeleteUsernameCommand } from '../../commands/delete-username.command'; import { DeleteUsernameCommand } from '../../commands/delete-username.command';
import { DeleteUsernameRequest } from '../../domain/dtos/delete-username.request'; import { DeleteUsernameRequest } from '../../domain/dtos/delete-username.request';
@ -65,6 +66,10 @@ const mockUsernameRepository = {
delete: jest.fn().mockResolvedValue(undefined), delete: jest.fn().mockResolvedValue(undefined),
}; };
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
describe('DeleteUsernameUseCase', () => { describe('DeleteUsernameUseCase', () => {
let deleteUsernameUseCase: DeleteUsernameUseCase; let deleteUsernameUseCase: DeleteUsernameUseCase;
@ -76,6 +81,10 @@ describe('DeleteUsernameUseCase', () => {
provide: UsernameRepository, provide: UsernameRepository,
useValue: mockUsernameRepository, useValue: mockUsernameRepository,
}, },
{
provide: LoggingMessager,
useValue: mockMessager,
},
DeleteUsernameUseCase, DeleteUsernameUseCase,
], ],
}).compile(); }).compile();

View File

@ -7,6 +7,7 @@ import * as bcrypt from 'bcrypt';
import { UpdatePasswordRequest } from '../../domain/dtos/update-password.request'; import { UpdatePasswordRequest } from '../../domain/dtos/update-password.request';
import { UpdatePasswordCommand } from '../../commands/update-password.command'; import { UpdatePasswordCommand } from '../../commands/update-password.command';
import { UpdatePasswordUseCase } from '../../domain/usecases/update-password.usecase'; import { UpdatePasswordUseCase } from '../../domain/usecases/update-password.usecase';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
const updatePasswordRequest: UpdatePasswordRequest = { const updatePasswordRequest: UpdatePasswordRequest = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
@ -23,6 +24,10 @@ const mockAuthRepository = {
}), }),
}; };
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
describe('UpdatePasswordUseCase', () => { describe('UpdatePasswordUseCase', () => {
let updatePasswordUseCase: UpdatePasswordUseCase; let updatePasswordUseCase: UpdatePasswordUseCase;
@ -34,6 +39,10 @@ describe('UpdatePasswordUseCase', () => {
provide: AuthRepository, provide: AuthRepository,
useValue: mockAuthRepository, useValue: mockAuthRepository,
}, },
{
provide: LoggingMessager,
useValue: mockMessager,
},
UpdatePasswordUseCase, UpdatePasswordUseCase,
], ],
}).compile(); }).compile();

View File

@ -7,18 +7,61 @@ import { UpdateUsernameRequest } from '../../domain/dtos/update-username.request
import { UpdateUsernameCommand } from '../../commands/update-username.command'; import { UpdateUsernameCommand } from '../../commands/update-username.command';
import { Type } from '../../domain/dtos/type.enum'; import { Type } from '../../domain/dtos/type.enum';
import { UpdateUsernameUseCase } from '../../domain/usecases/update-username.usecase'; import { UpdateUsernameUseCase } from '../../domain/usecases/update-username.usecase';
import { CommandBus } from '@nestjs/cqrs';
import { UsernameProfile } from '../../mappers/username.profile';
import { BadRequestException } from '@nestjs/common';
import { LoggingMessager } from '../../adapters/secondaries/logging.messager';
const existingUsername = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
username: 'john.doe@email.com',
type: Type.EMAIL,
};
const updateUsernameRequest: UpdateUsernameRequest = { const updateUsernameRequest: UpdateUsernameRequest = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
username: 'johnny.doe@email.com', username: 'johnny.doe@email.com',
type: Type.EMAIL, type: Type.EMAIL,
}; };
const newUsernameRequest: UpdateUsernameRequest = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
username: '+33611223344',
type: Type.PHONE,
};
const invalidUpdateUsernameRequest: UpdateUsernameRequest = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
username: '',
type: Type.EMAIL,
};
const updateUsernameCommand: UpdateUsernameCommand = new UpdateUsernameCommand( const updateUsernameCommand: UpdateUsernameCommand = new UpdateUsernameCommand(
updateUsernameRequest, updateUsernameRequest,
); );
const newUsernameCommand: UpdateUsernameCommand = new UpdateUsernameCommand(
newUsernameRequest,
);
const invalidUpdateUsernameCommand: UpdateUsernameCommand =
new UpdateUsernameCommand(invalidUpdateUsernameRequest);
const mockUsernameRepository = { const mockUsernameRepository = {
updateWhere: jest.fn().mockResolvedValue(updateUsernameRequest), findOne: jest.fn().mockResolvedValue(existingUsername),
updateWhere: jest
.fn()
.mockResolvedValueOnce(updateUsernameRequest)
.mockResolvedValueOnce(newUsernameRequest)
.mockResolvedValueOnce(invalidUpdateUsernameRequest),
};
const mockAddUsernameCommand = {
execute: jest.fn().mockResolvedValue(newUsernameRequest),
};
const mockMessager = {
publish: jest.fn().mockImplementation(),
}; };
describe('UpdateUsernameUseCase', () => { describe('UpdateUsernameUseCase', () => {
@ -32,7 +75,16 @@ describe('UpdateUsernameUseCase', () => {
provide: UsernameRepository, provide: UsernameRepository,
useValue: mockUsernameRepository, useValue: mockUsernameRepository,
}, },
{
provide: CommandBus,
useValue: mockAddUsernameCommand,
},
{
provide: LoggingMessager,
useValue: mockMessager,
},
UpdateUsernameUseCase, UpdateUsernameUseCase,
UsernameProfile,
], ],
}).compile(); }).compile();
@ -54,5 +106,20 @@ describe('UpdateUsernameUseCase', () => {
expect(updatedUsername.username).toBe(updateUsernameRequest.username); expect(updatedUsername.username).toBe(updateUsernameRequest.username);
expect(updatedUsername.type).toBe(updateUsernameRequest.type); expect(updatedUsername.type).toBe(updateUsernameRequest.type);
}); });
it('should create a new username', async () => {
const newUsername: Username = await updateUsernameUseCase.execute(
newUsernameCommand,
);
expect(newUsername.username).toBe(newUsernameRequest.username);
expect(newUsername.type).toBe(newUsernameRequest.type);
});
it('should throw an exception if username is invalid', async () => {
await expect(
updateUsernameUseCase.execute(invalidUpdateUsernameCommand),
).rejects.toBeInstanceOf(BadRequestException);
});
}); });
}); });

View File

@ -131,6 +131,7 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
return updatedEntity; return updatedEntity;
} catch (e) { } catch (e) {
console.log('error', e);
if (e instanceof PrismaClientKnownRequestError) { if (e instanceof PrismaClientKnownRequestError) {
throw new DatabaseException( throw new DatabaseException(
PrismaClientKnownRequestError.name, PrismaClientKnownRequestError.name,