wip tests update password
This commit is contained in:
parent
55c4367702
commit
de81325750
|
@ -31,12 +31,14 @@ export class AuthenticationMapper
|
|||
const record: AuthenticationWriteModel = {
|
||||
uuid: copy.id,
|
||||
password: copy.password,
|
||||
usernames: {
|
||||
usernames: copy.usernames
|
||||
? {
|
||||
create: copy.usernames.map((username: UsernameProps) => ({
|
||||
username: username.name,
|
||||
type: username.type,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
createdAt: copy.createdAt,
|
||||
updatedAt: copy.updatedAt,
|
||||
};
|
||||
|
@ -51,7 +53,7 @@ export class AuthenticationMapper
|
|||
props: {
|
||||
userId: record.uuid,
|
||||
password: record.password,
|
||||
usernames: record.usernames.map((username: UsernameModel) => ({
|
||||
usernames: record.usernames?.map((username: UsernameModel) => ({
|
||||
userId: record.uuid,
|
||||
name: username.username,
|
||||
type: Type[username.type],
|
||||
|
|
|
@ -21,6 +21,8 @@ import { DeleteUsernameGrpcController } from './interface/grpc-controllers/delet
|
|||
import { DeleteUsernameService } from './core/application/commands/delete-username/delete-username.service';
|
||||
import { UpdateUsernameGrpcController } from './interface/grpc-controllers/update-username.grpc.controller';
|
||||
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 = [
|
||||
CreateAuthenticationGrpcController,
|
||||
|
@ -28,6 +30,7 @@ const grpcControllers = [
|
|||
AddUsernameGrpcController,
|
||||
UpdateUsernameGrpcController,
|
||||
DeleteUsernameGrpcController,
|
||||
UpdatePasswordGrpcController,
|
||||
];
|
||||
|
||||
const commandHandlers: Provider[] = [
|
||||
|
@ -36,6 +39,7 @@ const commandHandlers: Provider[] = [
|
|||
AddUsernameService,
|
||||
UpdateUsernameService,
|
||||
DeleteUsernameService,
|
||||
UpdatePasswordService,
|
||||
];
|
||||
|
||||
const mappers: Provider[] = [AuthenticationMapper, UsernameMapper];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from './authentication.types';
|
||||
import { AuthenticationCreatedDomainEvent } from './events/authentication-created.domain-event';
|
||||
import { AuthenticationDeletedDomainEvent } from './events/authentication-deleted.domain-event';
|
||||
import { PasswordUpdatedDomainEvent } from './events/password-updated.domain-event';
|
||||
|
||||
export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
|
||||
protected readonly _id: AggregateID;
|
||||
|
@ -14,7 +15,7 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
|
|||
create: CreateAuthenticationProps,
|
||||
): Promise<AuthenticationEntity> => {
|
||||
const props: AuthenticationProps = { ...create };
|
||||
const hash = await bcrypt.hash(props.password, 10);
|
||||
const hash = await AuthenticationEntity.encryptPassword(props.password);
|
||||
const authentication = new AuthenticationEntity({
|
||||
id: props.userId,
|
||||
props: {
|
||||
|
@ -29,6 +30,15 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
|
|||
return authentication;
|
||||
};
|
||||
|
||||
updatePassword = async (password: string): Promise<void> => {
|
||||
this.props.password = await AuthenticationEntity.encryptPassword(password);
|
||||
this.addEvent(
|
||||
new PasswordUpdatedDomainEvent({
|
||||
aggregateId: this.id,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
delete(): void {
|
||||
this.addEvent(
|
||||
new AuthenticationDeletedDomainEvent({
|
||||
|
@ -40,4 +50,7 @@ export class AuthenticationEntity extends AggregateRoot<AuthenticationProps> {
|
|||
validate(): void {
|
||||
// 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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
|
||||
|
||||
export class PasswordUpdatedDomainEvent extends DomainEvent {
|
||||
constructor(props: DomainEventProps<PasswordUpdatedDomainEvent>) {
|
||||
super(props);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class UpdatePasswordRequestDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
userId: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue