diff --git a/.env b/.env index 9475988..42e21b7 100644 --- a/.env +++ b/.env @@ -7,8 +7,6 @@ SERVICE_PORT=5002 DATABASE_URL="postgresql://auth:auth@v3-auth-db:5432/auth?schema=public" # RABBIT MQ -RMQ_EXCHANGE_NAME=user -RMQ_EXCHANGE_TYPE=topic RMQ_URI=amqp://v3-gateway-broker:5672 # POSTGRES diff --git a/.env.dist b/.env.dist index 89d3ce5..20f7a25 100644 --- a/.env.dist +++ b/.env.dist @@ -7,8 +7,6 @@ SERVICE_PORT=5002 DATABASE_URL="postgresql://auth:auth@db:5432/auth?schema=public" # RABBIT MQ -RMQ_EXCHANGES=user -RMQ_EXCHANGE_TYPE=topic RMQ_URI=amqp://localhost:5672 # POSTGRES diff --git a/src/modules/auth/adapters/primaries/auth-messager.controller.ts b/src/modules/auth/adapters/primaries/auth-messager.controller.ts index 16eb156..7331e7b 100644 --- a/src/modules/auth/adapters/primaries/auth-messager.controller.ts +++ b/src/modules/auth/adapters/primaries/auth-messager.controller.ts @@ -13,26 +13,27 @@ export class AuthMessagerController { @RabbitSubscribe({ exchange: 'user', - routingKey: 'user.update', + routingKey: 'update', + queue: 'auth-user-update', }) public async userUpdatedHandler(message: string) { const updatedUser = JSON.parse(message); if (!updatedUser.hasOwnProperty('uuid')) throw new Error(); - if (updatedUser.hasOwnProperty('email')) { + if (updatedUser.hasOwnProperty('email') && updatedUser.email) { const updateUsernameRequest = new UpdateUsernameRequest(); updateUsernameRequest.uuid = updatedUser.uuid; updateUsernameRequest.username = updatedUser.email; updateUsernameRequest.type = Type.EMAIL; - this._commandBus.execute( + await this._commandBus.execute( new UpdateUsernameCommand(updateUsernameRequest), ); } - if (updatedUser.hasOwnProperty('phone')) { + if (updatedUser.hasOwnProperty('phone') && updatedUser.phone) { const updateUsernameRequest = new UpdateUsernameRequest(); updateUsernameRequest.uuid = updatedUser.uuid; updateUsernameRequest.username = updatedUser.phone; updateUsernameRequest.type = Type.PHONE; - this._commandBus.execute( + await this._commandBus.execute( new UpdateUsernameCommand(updateUsernameRequest), ); } @@ -40,13 +41,14 @@ export class AuthMessagerController { @RabbitSubscribe({ exchange: 'user', - routingKey: 'user.delete', + routingKey: 'delete', + queue: 'auth-user-delete', }) public async userDeletedHandler(message: string) { const deletedUser = JSON.parse(message); if (!deletedUser.hasOwnProperty('uuid')) throw new Error(); const deleteAuthRequest = new DeleteAuthRequest(); deleteAuthRequest.uuid = deletedUser.uuid; - this._commandBus.execute(new DeleteAuthCommand(deleteAuthRequest)); + await this._commandBus.execute(new DeleteAuthCommand(deleteAuthRequest)); } } diff --git a/src/modules/auth/adapters/primaries/auth.controller.ts b/src/modules/auth/adapters/primaries/auth.controller.ts index 161f123..515b62c 100644 --- a/src/modules/auth/adapters/primaries/auth.controller.ts +++ b/src/modules/auth/adapters/primaries/auth.controller.ts @@ -1,6 +1,6 @@ import { Mapper } from '@automapper/core'; import { InjectMapper } from '@automapper/nestjs'; -import { Controller } from '@nestjs/common'; +import { Controller, UsePipes } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { GrpcMethod, RpcException } from '@nestjs/microservices'; 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 { ValidateAuthQuery } from '../../queries/validate-auth.query'; import { AuthPresenter } from './auth.presenter'; +import { RpcValidationPipe } from './rpc.validation-pipe'; import { UsernamePresenter } from './username.presenter'; +@UsePipes( + new RpcValidationPipe({ + whitelist: true, + forbidUnknownValues: false, + }), +) @Controller() export class AuthController { constructor( diff --git a/src/modules/auth/adapters/primaries/rpc.validation-pipe.ts b/src/modules/auth/adapters/primaries/rpc.validation-pipe.ts new file mode 100644 index 0000000..f2b8c19 --- /dev/null +++ b/src/modules/auth/adapters/primaries/rpc.validation-pipe.ts @@ -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), + }); + }; + } +} diff --git a/src/modules/auth/adapters/secondaries/auth.messager.ts b/src/modules/auth/adapters/secondaries/auth.messager.ts new file mode 100644 index 0000000..7bf4e2e --- /dev/null +++ b/src/modules/auth/adapters/secondaries/auth.messager.ts @@ -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); + } +} diff --git a/src/modules/auth/adapters/secondaries/logging.messager.ts b/src/modules/auth/adapters/secondaries/logging.messager.ts new file mode 100644 index 0000000..00988ec --- /dev/null +++ b/src/modules/auth/adapters/secondaries/logging.messager.ts @@ -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); + } +} diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 7f0e887..38a5720 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -25,8 +25,12 @@ import { AuthMessagerController } from './adapters/primaries/auth-messager.contr useFactory: async (configService: ConfigService) => ({ exchanges: [ { - name: configService.get('RMQ_EXCHANGE_NAME'), - type: configService.get('RMQ_EXCHANGE_TYPE'), + name: 'user', + type: 'topic', + }, + { + name: 'logging', + type: 'topic', }, ], uri: configService.get('RMQ_URI'), diff --git a/src/modules/auth/domain/interfaces/message-broker.ts b/src/modules/auth/domain/interfaces/message-broker.ts new file mode 100644 index 0000000..594aa43 --- /dev/null +++ b/src/modules/auth/domain/interfaces/message-broker.ts @@ -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; +} diff --git a/src/modules/auth/domain/usecases/add-username.usecase.ts b/src/modules/auth/domain/usecases/add-username.usecase.ts index 2469d51..57752d5 100644 --- a/src/modules/auth/domain/usecases/add-username.usecase.ts +++ b/src/modules/auth/domain/usecases/add-username.usecase.ts @@ -1,11 +1,15 @@ import { CommandHandler } from '@nestjs/cqrs'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { AddUsernameCommand } from '../../commands/add-username.command'; import { Username } from '../entities/username'; @CommandHandler(AddUsernameCommand) export class AddUsernameUseCase { - constructor(private readonly _usernameRepository: UsernameRepository) {} + constructor( + private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, + ) {} async execute(command: AddUsernameCommand): Promise { const { uuid, username, type } = command.addUsernameRequest; @@ -15,8 +19,15 @@ export class AddUsernameUseCase { type, username, }); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.username.add.warning', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/create-auth.usecase.ts b/src/modules/auth/domain/usecases/create-auth.usecase.ts index 978b7be..c4e0a07 100644 --- a/src/modules/auth/domain/usecases/create-auth.usecase.ts +++ b/src/modules/auth/domain/usecases/create-auth.usecase.ts @@ -4,12 +4,14 @@ import { CreateAuthCommand } from '../../commands/create-auth.command'; import { Auth } from '../entities/auth'; import * as bcrypt from 'bcrypt'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; @CommandHandler(CreateAuthCommand) export class CreateAuthUseCase { constructor( private readonly _authRepository: AuthRepository, private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, ) {} async execute(command: CreateAuthCommand): Promise { @@ -28,8 +30,15 @@ export class CreateAuthUseCase { }); return auth; - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.create.critical', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/delete-auth.usecase.ts b/src/modules/auth/domain/usecases/delete-auth.usecase.ts index 4bcbb02..07905ce 100644 --- a/src/modules/auth/domain/usecases/delete-auth.usecase.ts +++ b/src/modules/auth/domain/usecases/delete-auth.usecase.ts @@ -1,5 +1,6 @@ import { CommandHandler } from '@nestjs/cqrs'; import { AuthRepository } from '../../adapters/secondaries/auth.repository'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteAuthCommand } from '../../commands/delete-auth.command'; @@ -8,6 +9,7 @@ export class DeleteAuthUseCase { constructor( private readonly _authRepository: AuthRepository, private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, ) {} async execute(command: DeleteAuthCommand) { @@ -18,8 +20,15 @@ export class DeleteAuthUseCase { return await this._authRepository.delete({ uuid: command.deleteAuthRequest.uuid, }); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.delete.critical', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/delete-username.usecase.ts b/src/modules/auth/domain/usecases/delete-username.usecase.ts index 8d5250a..27e7800 100644 --- a/src/modules/auth/domain/usecases/delete-username.usecase.ts +++ b/src/modules/auth/domain/usecases/delete-username.usecase.ts @@ -1,11 +1,15 @@ import { UnauthorizedException } from '@nestjs/common'; import { CommandHandler } from '@nestjs/cqrs'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteUsernameCommand } from '../../commands/delete-username.command'; @CommandHandler(DeleteUsernameCommand) export class DeleteUsernameUseCase { - constructor(private readonly _usernameRepository: UsernameRepository) {} + constructor( + private readonly _usernameRepository: UsernameRepository, + private readonly _loggingMessager: LoggingMessager, + ) {} async execute(command: DeleteUsernameCommand) { try { @@ -20,8 +24,15 @@ export class DeleteUsernameUseCase { return await this._usernameRepository.delete({ username }); } throw new UnauthorizedException(); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.username.delete.warning', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/update-password.usecase.ts b/src/modules/auth/domain/usecases/update-password.usecase.ts index 41074ba..b8bf22d 100644 --- a/src/modules/auth/domain/usecases/update-password.usecase.ts +++ b/src/modules/auth/domain/usecases/update-password.usecase.ts @@ -3,10 +3,14 @@ import { AuthRepository } from '../../adapters/secondaries/auth.repository'; import { Auth } from '../entities/auth'; import * as bcrypt from 'bcrypt'; import { UpdatePasswordCommand } from '../../commands/update-password.command'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; @CommandHandler(UpdatePasswordCommand) export class UpdatePasswordUseCase { - constructor(private readonly _authRepository: AuthRepository) {} + constructor( + private readonly _authRepository: AuthRepository, + private readonly _loggingMessager: LoggingMessager, + ) {} async execute(command: UpdatePasswordCommand): Promise { const { uuid, password } = command.updatePasswordRequest; @@ -16,8 +20,15 @@ export class UpdatePasswordUseCase { return await this._authRepository.update(uuid, { password: hash, }); - } catch (e) { - throw e; + } catch (error) { + this._loggingMessager.publish( + 'auth.password.update.warning', + JSON.stringify({ + command, + error, + }), + ); + throw error; } } } diff --git a/src/modules/auth/domain/usecases/update-username.usecase.ts b/src/modules/auth/domain/usecases/update-username.usecase.ts index f18b912..35c51ac 100644 --- a/src/modules/auth/domain/usecases/update-username.usecase.ts +++ b/src/modules/auth/domain/usecases/update-username.usecase.ts @@ -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 { AddUsernameCommand } from '../../commands/add-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'; @CommandHandler(UpdateUsernameCommand) 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 { const { uuid, username, type } = command.updateUsernameRequest; - try { - return await this._usernameRepository.updateWhere( - { - uuid_type: { - uuid, - type, + if (!username) throw new BadRequestException(); + // update username if it exists, otherwise create it + const existingUsername = await this._usernameRepository.findOne({ + uuid, + 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) { throw e; diff --git a/src/modules/auth/mappers/username.profile.ts b/src/modules/auth/mappers/username.profile.ts index 9eeb8b2..af2cc76 100644 --- a/src/modules/auth/mappers/username.profile.ts +++ b/src/modules/auth/mappers/username.profile.ts @@ -2,6 +2,8 @@ import { createMap, Mapper } from '@automapper/core'; import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; import { Injectable } from '@nestjs/common'; 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'; @Injectable() @@ -13,6 +15,7 @@ export class UsernameProfile extends AutomapperProfile { override get profile() { return (mapper: any) => { createMap(mapper, Username, UsernamePresenter); + createMap(mapper, UpdateUsernameRequest, AddUsernameRequest); }; } } diff --git a/src/modules/auth/tests/unit/add-username.usecase.spec.ts b/src/modules/auth/tests/unit/add-username.usecase.spec.ts index 6062794..092e830 100644 --- a/src/modules/auth/tests/unit/add-username.usecase.spec.ts +++ b/src/modules/auth/tests/unit/add-username.usecase.spec.ts @@ -8,6 +8,7 @@ import { Type } from '../../domain/dtos/type.enum'; import { AddUsernameRequest } from '../../domain/dtos/add-username.request'; import { AddUsernameCommand } from '../../commands/add-username.command'; import { AddUsernameUseCase } from '../../domain/usecases/add-username.usecase'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; const addUsernameRequest: AddUsernameRequest = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', @@ -22,6 +23,10 @@ const mockUsernameRepository = { create: jest.fn().mockResolvedValue(addUsernameRequest), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('AddUsernameUseCase', () => { let addUsernameUseCase: AddUsernameUseCase; @@ -33,6 +38,10 @@ describe('AddUsernameUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, AddUsernameUseCase, AuthProfile, ], diff --git a/src/modules/auth/tests/unit/create-auth.usecase.spec.ts b/src/modules/auth/tests/unit/create-auth.usecase.spec.ts index f536b9c..e93fb00 100644 --- a/src/modules/auth/tests/unit/create-auth.usecase.spec.ts +++ b/src/modules/auth/tests/unit/create-auth.usecase.spec.ts @@ -9,6 +9,7 @@ import { CreateAuthUseCase } from '../../domain/usecases/create-auth.usecase'; import * as bcrypt from 'bcrypt'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { Type } from '../../domain/dtos/type.enum'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; const newAuthRequest: CreateAuthRequest = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', @@ -33,6 +34,10 @@ const mockUsernameRepository = { }), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('CreateAuthUseCase', () => { let createAuthUseCase: CreateAuthUseCase; @@ -48,6 +53,10 @@ describe('CreateAuthUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, CreateAuthUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts b/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts index c0b5117..9db5ab2 100644 --- a/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts +++ b/src/modules/auth/tests/unit/delete-auth.usecase.spec.ts @@ -2,6 +2,7 @@ import { classes } from '@automapper/classes'; import { AutomapperModule } from '@automapper/nestjs'; import { Test, TestingModule } from '@nestjs/testing'; import { AuthRepository } from '../../adapters/secondaries/auth.repository'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteAuthCommand } from '../../commands/delete-auth.command'; import { DeleteAuthRequest } from '../../domain/dtos/delete-auth.request'; @@ -44,6 +45,10 @@ const mockUsernameRepository = { deleteMany: jest.fn().mockResolvedValue(undefined), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('DeleteAuthUseCase', () => { let deleteAuthUseCase: DeleteAuthUseCase; @@ -59,6 +64,10 @@ describe('DeleteAuthUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, DeleteAuthUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/delete-username.usecase.spec.ts b/src/modules/auth/tests/unit/delete-username.usecase.spec.ts index 3e8cdd1..1e583c8 100644 --- a/src/modules/auth/tests/unit/delete-username.usecase.spec.ts +++ b/src/modules/auth/tests/unit/delete-username.usecase.spec.ts @@ -2,6 +2,7 @@ import { classes } from '@automapper/classes'; import { AutomapperModule } from '@automapper/nestjs'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; import { UsernameRepository } from '../../adapters/secondaries/username.repository'; import { DeleteUsernameCommand } from '../../commands/delete-username.command'; import { DeleteUsernameRequest } from '../../domain/dtos/delete-username.request'; @@ -65,6 +66,10 @@ const mockUsernameRepository = { delete: jest.fn().mockResolvedValue(undefined), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('DeleteUsernameUseCase', () => { let deleteUsernameUseCase: DeleteUsernameUseCase; @@ -76,6 +81,10 @@ describe('DeleteUsernameUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, DeleteUsernameUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/update-password.usecase.spec.ts b/src/modules/auth/tests/unit/update-password.usecase.spec.ts index bd388c2..b074c5b 100644 --- a/src/modules/auth/tests/unit/update-password.usecase.spec.ts +++ b/src/modules/auth/tests/unit/update-password.usecase.spec.ts @@ -7,6 +7,7 @@ import * as bcrypt from 'bcrypt'; import { UpdatePasswordRequest } from '../../domain/dtos/update-password.request'; import { UpdatePasswordCommand } from '../../commands/update-password.command'; import { UpdatePasswordUseCase } from '../../domain/usecases/update-password.usecase'; +import { LoggingMessager } from '../../adapters/secondaries/logging.messager'; const updatePasswordRequest: UpdatePasswordRequest = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', @@ -23,6 +24,10 @@ const mockAuthRepository = { }), }; +const mockMessager = { + publish: jest.fn().mockImplementation(), +}; + describe('UpdatePasswordUseCase', () => { let updatePasswordUseCase: UpdatePasswordUseCase; @@ -34,6 +39,10 @@ describe('UpdatePasswordUseCase', () => { provide: AuthRepository, useValue: mockAuthRepository, }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, UpdatePasswordUseCase, ], }).compile(); diff --git a/src/modules/auth/tests/unit/update-username.usecase.spec.ts b/src/modules/auth/tests/unit/update-username.usecase.spec.ts index 1866eea..45e10e7 100644 --- a/src/modules/auth/tests/unit/update-username.usecase.spec.ts +++ b/src/modules/auth/tests/unit/update-username.usecase.spec.ts @@ -7,18 +7,61 @@ import { UpdateUsernameRequest } from '../../domain/dtos/update-username.request import { UpdateUsernameCommand } from '../../commands/update-username.command'; import { Type } from '../../domain/dtos/type.enum'; 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 = { uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', username: 'johnny.doe@email.com', 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( updateUsernameRequest, ); +const newUsernameCommand: UpdateUsernameCommand = new UpdateUsernameCommand( + newUsernameRequest, +); + +const invalidUpdateUsernameCommand: UpdateUsernameCommand = + new UpdateUsernameCommand(invalidUpdateUsernameRequest); + 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', () => { @@ -32,7 +75,16 @@ describe('UpdateUsernameUseCase', () => { provide: UsernameRepository, useValue: mockUsernameRepository, }, + { + provide: CommandBus, + useValue: mockAddUsernameCommand, + }, + { + provide: LoggingMessager, + useValue: mockMessager, + }, UpdateUsernameUseCase, + UsernameProfile, ], }).compile(); @@ -54,5 +106,20 @@ describe('UpdateUsernameUseCase', () => { expect(updatedUsername.username).toBe(updateUsernameRequest.username); 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); + }); }); }); diff --git a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts index f60e0e4..fd2bf9a 100644 --- a/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts +++ b/src/modules/database/src/adapters/secondaries/prisma-repository.abstract.ts @@ -131,6 +131,7 @@ export abstract class PrismaRepository implements IRepository { return updatedEntity; } catch (e) { + console.log('error', e); if (e instanceof PrismaClientKnownRequestError) { throw new DatabaseException( PrismaClientKnownRequestError.name,