wip tests update password

This commit is contained in:
sbriat 2023-07-10 17:37:50 +02:00
parent 55c4367702
commit de81325750
9 changed files with 182 additions and 8 deletions

View File

@ -31,12 +31,14 @@ export class AuthenticationMapper
const record: AuthenticationWriteModel = { const record: AuthenticationWriteModel = {
uuid: copy.id, uuid: copy.id,
password: copy.password, password: copy.password,
usernames: { usernames: copy.usernames
? {
create: copy.usernames.map((username: UsernameProps) => ({ create: copy.usernames.map((username: UsernameProps) => ({
username: username.name, username: username.name,
type: username.type, type: username.type,
})), })),
}, }
: undefined,
createdAt: copy.createdAt, createdAt: copy.createdAt,
updatedAt: copy.updatedAt, updatedAt: copy.updatedAt,
}; };
@ -51,7 +53,7 @@ export class AuthenticationMapper
props: { props: {
userId: record.uuid, userId: record.uuid,
password: record.password, password: record.password,
usernames: record.usernames.map((username: UsernameModel) => ({ usernames: record.usernames?.map((username: UsernameModel) => ({
userId: record.uuid, userId: record.uuid,
name: username.username, name: username.username,
type: Type[username.type], type: Type[username.type],

View File

@ -21,6 +21,8 @@ import { DeleteUsernameGrpcController } from './interface/grpc-controllers/delet
import { DeleteUsernameService } from './core/application/commands/delete-username/delete-username.service'; import { DeleteUsernameService } from './core/application/commands/delete-username/delete-username.service';
import { UpdateUsernameGrpcController } from './interface/grpc-controllers/update-username.grpc.controller'; import { UpdateUsernameGrpcController } from './interface/grpc-controllers/update-username.grpc.controller';
import { UpdateUsernameService } from './core/application/commands/update-username/update-username.service'; import { UpdateUsernameService } from './core/application/commands/update-username/update-username.service';
import { UpdatePasswordGrpcController } from './interface/grpc-controllers/update-password.grpc.controller';
import { UpdatePasswordService } from './core/application/commands/update-password/update-password.service';
const grpcControllers = [ const grpcControllers = [
CreateAuthenticationGrpcController, CreateAuthenticationGrpcController,
@ -28,6 +30,7 @@ const grpcControllers = [
AddUsernameGrpcController, AddUsernameGrpcController,
UpdateUsernameGrpcController, UpdateUsernameGrpcController,
DeleteUsernameGrpcController, DeleteUsernameGrpcController,
UpdatePasswordGrpcController,
]; ];
const commandHandlers: Provider[] = [ const commandHandlers: Provider[] = [
@ -36,6 +39,7 @@ const commandHandlers: Provider[] = [
AddUsernameService, AddUsernameService,
UpdateUsernameService, UpdateUsernameService,
DeleteUsernameService, DeleteUsernameService,
UpdatePasswordService,
]; ];
const mappers: Provider[] = [AuthenticationMapper, UsernameMapper]; const mappers: Provider[] = [AuthenticationMapper, UsernameMapper];

View File

@ -0,0 +1,12 @@
import { Command, CommandProps } from '@mobicoop/ddd-library';
export class UpdatePasswordCommand extends Command {
readonly userId: string;
readonly password: string;
constructor(props: CommandProps<UpdatePasswordCommand>) {
super(props);
this.userId = props.userId;
this.password = props.password;
}
}

View File

@ -0,0 +1,23 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { Inject } from '@nestjs/common';
import { AggregateID } from '@mobicoop/ddd-library';
import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
import { UpdatePasswordCommand } from './update-password.command';
import { AuthenticationRepositoryPort } from '../../ports/authentication.repository.port';
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
@CommandHandler(UpdatePasswordCommand)
export class UpdatePasswordService implements ICommandHandler {
constructor(
@Inject(AUTHENTICATION_REPOSITORY)
private readonly authenticationRepository: AuthenticationRepositoryPort,
) {}
async execute(command: UpdatePasswordCommand): Promise<AggregateID> {
const authentication: AuthenticationEntity =
await this.authenticationRepository.findOneById(command.userId);
await authentication.updatePassword(command.password);
await this.authenticationRepository.update(command.userId, authentication);
return authentication.id;
}
}

View File

@ -6,6 +6,7 @@ import {
} from './authentication.types'; } from './authentication.types';
import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-event'; import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-event';
import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event'; import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event';
import { PasswordUpdatedDomainEvent } from './events/password-updated.domain-event';
export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> { export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
protected readonly _id: AggregateID; protected readonly _id: AggregateID;
@ -14,7 +15,7 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
create: CreateAuthenticationProps, create: CreateAuthenticationProps,
): Promise<AuthenticationEntity> => { ): Promise<AuthenticationEntity> => {
const props: AuthenticationProps = { ...create }; const props: AuthenticationProps = { ...create };
const hash = await bcrypt.hash(props.password, 10); const hash = await AuthenticationEntity.encryptPassword(props.password);
const authentication = new AuthenticationEntity({ const authentication = new AuthenticationEntity({
id: props.userId, id: props.userId,
props: { props: {
@ -29,6 +30,15 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
return authentication; return authentication;
}; };
updatePassword = async (password: string): Promise<void> => {
this.props.password = await AuthenticationEntity.encryptPassword(password);
this.addEvent(
new PasswordUpdatedDomainEvent({
aggregateId: this.id,
}),
);
};
delete(): void { delete(): void {
this.addEvent( this.addEvent(
new AuthenticationDeletedDomainEvent({ new AuthenticationDeletedDomainEvent({
@ -40,4 +50,7 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
validate(): void { validate(): void {
// entity business rules validation to protect it's invariant before saving entity to a database // entity business rules validation to protect it's invariant before saving entity to a database
} }
private static encryptPassword = async (password: string): Promise<string> =>
await bcrypt.hash(password, 10);
} }

View File

@ -0,0 +1,7 @@
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
export class PasswordUpdatedDomainEvent extends DomainEvent {
constructor(props: DomainEventProps<PasswordUpdatedDomainEvent>) {
super(props);
}
}

View File

@ -0,0 +1,11 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class UpdatePasswordRequestDto {
@IsString()
@IsNotEmpty()
userId: string;
@IsString()
@IsNotEmpty()
password: string;
}

View File

@ -0,0 +1,40 @@
import {
AggregateID,
IdResponse,
RpcExceptionCode,
RpcValidationPipe,
} from '@mobicoop/ddd-library';
import { Controller, UsePipes } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { UpdatePasswordRequestDto } from './dtos/update-password.request.dto';
import { UpdatePasswordCommand } from '@modules/authentication/core/application/commands/update-password/update-password.command';
@UsePipes(
new RpcValidationPipe({
whitelist: true,
forbidUnknownValues: false,
}),
)
@Controller()
export class UpdatePasswordGrpcController {
constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod('AuthenticationService', 'UpdatePassword')
async updatePassword(data: UpdatePasswordRequestDto): Promise<IdResponse> {
try {
const aggregateID: AggregateID = await this.commandBus.execute(
new UpdatePasswordCommand({
userId: data.userId,
password: data.password,
}),
);
return new IdResponse(aggregateID);
} catch (error: any) {
throw new RpcException({
code: RpcExceptionCode.UNKNOWN,
message: error.message,
});
}
}
}

View File

@ -0,0 +1,62 @@
import { AggregateID } from '@mobicoop/ddd-library';
import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens';
import { UpdatePasswordService } from '@modules/authentication/core/application/commands/update-password/update-password.service';
import { Type } from '@modules/authentication/core/domain/username.types';
import { UpdatePasswordRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/update-password.request.dto';
import { Test, TestingModule } from '@nestjs/testing';
import { UpdatePasswordCommand } from '@modules/authentication/core/application/commands/update-password/update-password.command';
const updatePasswordRequest: UpdatePasswordRequestDto = {
userId: '165192d4-398a-4469-a16b-98c02cc6f531',
password: '@Br@ndN3wPa$$w0rd',
};
const mockAuthenticationRepository = {
findOneById: jest.fn().mockImplementation(() => ({
id: '165192d4-398a-4469-a16b-98c02cc6f531',
updatePassword: jest.fn(),
getProps: jest.fn().mockImplementation(() => ({
userId: '165192d4-398a-4469-a16b-98c02cc6f531',
name: 'john.doe@email.com',
type: Type.EMAIL,
})),
})),
update: jest.fn().mockImplementation(),
};
describe('Update Password Service', () => {
let updatePasswordService: UpdatePasswordService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: AUTHENTICATION_REPOSITORY,
useValue: mockAuthenticationRepository,
},
UpdatePasswordService,
],
}).compile();
updatePasswordService = module.get<UpdatePasswordService>(
UpdatePasswordService,
);
});
it('should be defined', () => {
expect(updatePasswordService).toBeDefined();
});
describe('execution', () => {
const updatePasswordCommand = new UpdatePasswordCommand({
userId: updatePasswordRequest.userId,
password: updatePasswordRequest.password,
});
it('should update the password', async () => {
const result: AggregateID = await updatePasswordService.execute(
updatePasswordCommand,
);
expect(result).toBe('165192d4-398a-4469-a16b-98c02cc6f531');
});
});
});