diff --git a/src/modules/authentication/core/domain/username.entity.ts b/src/modules/authentication/core/domain/username.entity.ts index 5fd83fa..d00806e 100644 --- a/src/modules/authentication/core/domain/username.entity.ts +++ b/src/modules/authentication/core/domain/username.entity.ts @@ -1,11 +1,13 @@ import { AggregateID, AggregateRoot } from '@mobicoop/ddd-library'; -import { UsernameProps } from './username.types'; +import { CreateUsernameProps, UsernameProps } from './username.types'; import { UsernameAddedDomainEvent } from './events/username-added.domain-event'; export class UsernameEntity extends AggregateRoot { protected readonly _id: AggregateID; - static create = async (create: UsernameProps): Promise => { + static create = async ( + create: CreateUsernameProps, + ): Promise => { const props: UsernameProps = { ...create }; const username = new UsernameEntity({ id: props.name, diff --git a/src/modules/authentication/core/domain/username.types.ts b/src/modules/authentication/core/domain/username.types.ts index 382ce83..6f671b0 100644 --- a/src/modules/authentication/core/domain/username.types.ts +++ b/src/modules/authentication/core/domain/username.types.ts @@ -1,10 +1,15 @@ -// All properties that a Username has export interface UsernameProps { name: string; userId?: string; type: Type; } +export interface CreateUsernameProps { + name: string; + userId: string; + type: Type; +} + export enum Type { EMAIL = 'EMAIL', PHONE = 'PHONE', diff --git a/src/modules/authentication/tests/unit/core/authentication.entity.spec.ts b/src/modules/authentication/tests/unit/core/authentication.entity.spec.ts new file mode 100644 index 0000000..e4e2655 --- /dev/null +++ b/src/modules/authentication/tests/unit/core/authentication.entity.spec.ts @@ -0,0 +1,61 @@ +import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity'; +import { CreateAuthenticationProps } from '@modules/authentication/core/domain/authentication.types'; +import { AuthenticationDeletedDomainEvent } from '@modules/authentication/core/domain/events/authentication-deleted.domain-event'; +import { Type } from '@modules/authentication/core/domain/username.types'; + +const createAuthenticationProps: CreateAuthenticationProps = { + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + password: 'somePassword', + usernames: [ + { + type: Type.EMAIL, + name: 'john.doe@email.com', + }, + ], +}; + +const createAuthenticationPropsWith2Usernames: CreateAuthenticationProps = { + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + password: 'somePassword', + usernames: [ + { + type: Type.EMAIL, + name: 'john.doe@email.com', + }, + { + type: Type.PHONE, + name: '+33611223344', + }, + ], +}; + +describe('Authentication entity create', () => { + it('should create a new authentication entity', async () => { + const authenticationEntity: AuthenticationEntity = + await AuthenticationEntity.create(createAuthenticationProps); + expect(authenticationEntity.id).toBe( + '165192d4-398a-4469-a16b-98c02cc6f531', + ); + expect(authenticationEntity.domainEvents.length).toBe(1); + }); + it('should create a new authentication entity with 2 usernames', async () => { + const authenticationEntity: AuthenticationEntity = + await AuthenticationEntity.create( + createAuthenticationPropsWith2Usernames, + ); + expect(authenticationEntity.id).toBe( + '165192d4-398a-4469-a16b-98c02cc6f531', + ); + expect(authenticationEntity.getProps().usernames.length).toBe(2); + expect(authenticationEntity.domainEvents.length).toBe(1); + }); + it('should delete an authentication entity', async () => { + const authenticationEntity: AuthenticationEntity = + await AuthenticationEntity.create(createAuthenticationProps); + authenticationEntity.delete(); + expect(authenticationEntity.domainEvents.length).toBe(2); + expect(authenticationEntity.domainEvents[1]).toBeInstanceOf( + AuthenticationDeletedDomainEvent, + ); + }); +}); diff --git a/src/modules/authentication/tests/unit/core/create-authentication.service.spec.ts b/src/modules/authentication/tests/unit/core/create-authentication.service.spec.ts new file mode 100644 index 0000000..b4b1d25 --- /dev/null +++ b/src/modules/authentication/tests/unit/core/create-authentication.service.spec.ts @@ -0,0 +1,131 @@ +import { + AggregateID, + ConflictException, + UniqueConstraintException, +} from '@mobicoop/ddd-library'; +import { AUTHENTICATION_REPOSITORY } from '@modules/authentication/authentication.di-tokens'; +import { CreateAuthenticationCommand } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.command'; +import { CreateAuthenticationService } from '@modules/authentication/core/application/commands/create-authentication/create-authentication.service'; +import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity'; +import { + AuthenticationAlreadyExistsException, + UsernameAlreadyExistsException, +} from '@modules/authentication/core/domain/authentication.errors'; +import { Type } from '@modules/authentication/core/domain/username.types'; +import { CreateAuthenticationRequestDto } from '@modules/authentication/interface/grpc-controllers/dtos/create-authentication.request.dto'; +import { Test, TestingModule } from '@nestjs/testing'; + +const createAuthenticationRequest: CreateAuthenticationRequestDto = { + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + password: 'somePassword', + usernames: [ + { + name: 'john.doe@email.com', + type: Type.EMAIL, + }, + ], +}; + +const mockAuthenticationRepository = { + insert: jest + .fn() + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => { + throw new ConflictException('already exists'); + }) + .mockImplementationOnce(() => { + throw new UniqueConstraintException('uuid'); + }) + .mockImplementationOnce(() => { + throw new UniqueConstraintException('username'); + }) + .mockImplementationOnce(() => { + throw new Error(); + }), +}; + +describe('create-authentication.service', () => { + let createAuthenticationService: CreateAuthenticationService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: AUTHENTICATION_REPOSITORY, + useValue: mockAuthenticationRepository, + }, + CreateAuthenticationService, + ], + }).compile(); + + createAuthenticationService = module.get( + CreateAuthenticationService, + ); + }); + + it('should be defined', () => { + expect(createAuthenticationService).toBeDefined(); + }); + + describe('execution', () => { + const createAdCommand = new CreateAuthenticationCommand( + createAuthenticationRequest, + ); + it('should create a new authentication', async () => { + AuthenticationEntity.create = jest.fn().mockReturnValue({ + id: '165192d4-398a-4469-a16b-98c02cc6f531', + getProps: jest.fn().mockImplementation(() => ({ + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + })), + }); + const result: AggregateID = await createAuthenticationService.execute( + createAdCommand, + ); + expect(result).toBe('165192d4-398a-4469-a16b-98c02cc6f531'); + }); + it('should throw a dedicated exception if Authentication already exists', async () => { + AuthenticationEntity.create = jest.fn().mockReturnValue({ + id: '165192d4-398a-4469-a16b-98c02cc6f531', + getProps: jest.fn().mockImplementation(() => ({ + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + })), + }); + await expect( + createAuthenticationService.execute(createAdCommand), + ).rejects.toBeInstanceOf(AuthenticationAlreadyExistsException); + }); + it('should throw a dedicated exception if uuid already exists', async () => { + AuthenticationEntity.create = jest.fn().mockReturnValue({ + id: '165192d4-398a-4469-a16b-98c02cc6f531', + getProps: jest.fn().mockImplementation(() => ({ + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + })), + }); + await expect( + createAuthenticationService.execute(createAdCommand), + ).rejects.toBeInstanceOf(AuthenticationAlreadyExistsException); + }); + it('should throw a dedicated exception if username already exists', async () => { + AuthenticationEntity.create = jest.fn().mockReturnValue({ + id: '165192d4-398a-4469-a16b-98c02cc6f531', + getProps: jest.fn().mockImplementation(() => ({ + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + })), + }); + await expect( + createAuthenticationService.execute(createAdCommand), + ).rejects.toBeInstanceOf(UsernameAlreadyExistsException); + }); + it('should throw an error if something bad happens', async () => { + AuthenticationEntity.create = jest.fn().mockReturnValue({ + id: '165192d4-398a-4469-a16b-98c02cc6f531', + getProps: jest.fn().mockImplementation(() => ({ + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + })), + }); + await expect( + createAuthenticationService.execute(createAdCommand), + ).rejects.toBeInstanceOf(Error); + }); + }); +}); diff --git a/src/modules/authentication/tests/unit/core/username.entity.spec.ts b/src/modules/authentication/tests/unit/core/username.entity.spec.ts new file mode 100644 index 0000000..2cb19c5 --- /dev/null +++ b/src/modules/authentication/tests/unit/core/username.entity.spec.ts @@ -0,0 +1,21 @@ +import { UsernameEntity } from '@modules/authentication/core/domain/username.entity'; +import { + CreateUsernameProps, + Type, +} from '@modules/authentication/core/domain/username.types'; + +const createUsernameProps: CreateUsernameProps = { + userId: '165192d4-398a-4469-a16b-98c02cc6f531', + type: Type.EMAIL, + name: 'john.doe@email.com', +}; + +describe('Username entity create', () => { + it('should create a new username entity', async () => { + const usernameEntity: UsernameEntity = await UsernameEntity.create( + createUsernameProps, + ); + expect(usernameEntity.id).toBe('john.doe@email.com'); + expect(usernameEntity.domainEvents.length).toBe(1); + }); +}); diff --git a/src/modules/authentication/tests/unit/infrastructure/authentication.repository.spec.ts b/src/modules/authentication/tests/unit/infrastructure/authentication.repository.spec.ts new file mode 100644 index 0000000..c7aec80 --- /dev/null +++ b/src/modules/authentication/tests/unit/infrastructure/authentication.repository.spec.ts @@ -0,0 +1,37 @@ +import { AuthenticationMapper } from '@modules/authentication/authentication.mapper'; +import { AuthenticationRepository } from '@modules/authentication/infrastructure/authentication.repository'; +import { PrismaService } from '@modules/authentication/infrastructure/prisma.service'; +import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter'; +import { Test, TestingModule } from '@nestjs/testing'; + +const mockMessagePublisher = { + publish: jest.fn().mockImplementation(), +}; + +describe('Authentication repository', () => { + let prismaService: PrismaService; + let authenticationMapper: AuthenticationMapper; + let eventEmitter: EventEmitter2; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [EventEmitterModule.forRoot()], + providers: [PrismaService, AuthenticationMapper], + }).compile(); + + prismaService = module.get(PrismaService); + authenticationMapper = + module.get(AuthenticationMapper); + eventEmitter = module.get(EventEmitter2); + }); + it('should be defined', () => { + expect( + new AuthenticationRepository( + prismaService, + authenticationMapper, + eventEmitter, + mockMessagePublisher, + ), + ).toBeDefined(); + }); +}); diff --git a/src/modules/authentication/tests/unit/infrastructure/username.repository.spec.ts b/src/modules/authentication/tests/unit/infrastructure/username.repository.spec.ts new file mode 100644 index 0000000..16f9124 --- /dev/null +++ b/src/modules/authentication/tests/unit/infrastructure/username.repository.spec.ts @@ -0,0 +1,36 @@ +import { UsernameMapper } from '@modules/authentication/username.mapper'; +import { PrismaService } from '@modules/authentication/infrastructure/prisma.service'; +import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter'; +import { Test, TestingModule } from '@nestjs/testing'; +import { UsernameRepository } from '@modules/authentication/infrastructure/username.repository'; + +const mockMessagePublisher = { + publish: jest.fn().mockImplementation(), +}; + +describe('Username repository', () => { + let prismaService: PrismaService; + let usernameMapper: UsernameMapper; + let eventEmitter: EventEmitter2; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [EventEmitterModule.forRoot()], + providers: [PrismaService, UsernameMapper], + }).compile(); + + prismaService = module.get(PrismaService); + usernameMapper = module.get(UsernameMapper); + eventEmitter = module.get(EventEmitter2); + }); + it('should be defined', () => { + expect( + new UsernameRepository( + prismaService, + usernameMapper, + eventEmitter, + mockMessagePublisher, + ), + ).toBeDefined(); + }); +});