wip tests update password
This commit is contained in:
parent
55c4367702
commit
de81325750
|
@ -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],
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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';
|
} 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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