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