diff --git a/package.json b/package.json index b9eab6b..026498c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "rabbitmq:configure": "npx ts-node ./src/scripts/configure-rabbitmq/configure-rabbitmq.ts", "rabbitmq:consume": "npx ts-node ./src/scripts/configure-rabbitmq/consume-rabbitmq.ts", "test:backoffice:backend:spotify-user":"npm run test:backoffice:backend:spotify-user:unit && npm run test:backoffice:backend:spotify-user:integration", - "test:backoffice:backend:spotify-user:unit":"cross-env NODE_ENV=test jest --passWithNoTests --testPathPattern './tests/contexts/backoffice/spotify-user/application/.*\\.test\\.ts'", + "test:backoffice:backend:clients:unit":"cross-env NODE_ENV=test jest --passWithNoTests --testPathPattern './tests/contexts/backoffice/clients/application/.*\\.test\\.ts'", "test:backoffice:backend:spotify-user:integration":"cross-env NODE_ENV=test NODE_OPTIONS=\"--experimental-vm-modules\" jest --passWithNoTests --testPathPattern './tests/contexts/backoffice/spotify-user/infrastructure/.*/.*\\.test\\.ts'" }, "dependencies": { diff --git a/src/contexts/backoffice/clients/application/register/ClientRegistrar.ts b/src/contexts/backoffice/clients/application/register/ClientRegistrar.ts new file mode 100644 index 0000000..b4d879a --- /dev/null +++ b/src/contexts/backoffice/clients/application/register/ClientRegistrar.ts @@ -0,0 +1,10 @@ +import Client from '../../domain/Client'; +import ClientRepository from '../../domain/ClientRepository'; +import RegisterClientCommand from './RegisterClientCommand'; + +export default class ClientRegistrar { + constructor(private readonly repository: ClientRepository) {} + async registar(command: RegisterClientCommand): Promise { + await Client.save(command)(this.repository); + } +} diff --git a/src/contexts/backoffice/clients/application/register/RegisterClientCommand.ts b/src/contexts/backoffice/clients/application/register/RegisterClientCommand.ts new file mode 100644 index 0000000..eb1ac28 --- /dev/null +++ b/src/contexts/backoffice/clients/application/register/RegisterClientCommand.ts @@ -0,0 +1,13 @@ +import Command from '../../../../../shared/domain/command/Command'; + +type CreateClientPrimitives = { + id: string; + name: string; + email: string; + phone: string; + company: string; + position: string; +}; +export default class RegisterClientCommand implements Command { + constructor(readonly data: CreateClientPrimitives) {} +} diff --git a/src/contexts/backoffice/clients/application/register/RegisterClientCommandHandler.ts b/src/contexts/backoffice/clients/application/register/RegisterClientCommandHandler.ts new file mode 100644 index 0000000..6885f8c --- /dev/null +++ b/src/contexts/backoffice/clients/application/register/RegisterClientCommandHandler.ts @@ -0,0 +1,16 @@ +import Command from '../../../../../shared/domain/command/Command'; +import CommandHandler from '../../../../../shared/domain/command/CommandHandler'; +import ClientRegistrar from './ClientRegistrar'; +import RegisterClientCommand from './RegisterClientCommand'; + +export default class RegisterClientCommandHandler implements CommandHandler { + constructor(private readonly registrar: ClientRegistrar) {} + + subscribedTo(): Command { + return RegisterClientCommand; + } + + async handle(command: RegisterClientCommand): Promise { + await this.registrar.registar(command); + } +} diff --git a/src/contexts/backoffice/clients/domain/Client.ts b/src/contexts/backoffice/clients/domain/Client.ts new file mode 100644 index 0000000..0843c14 --- /dev/null +++ b/src/contexts/backoffice/clients/domain/Client.ts @@ -0,0 +1,56 @@ +import RegisterClientCommand from '../application/register/RegisterClientCommand'; +import ClientCompany from './ClientCompany'; +import ClientEmail from './ClientEmail'; +import ClientId from './ClientId'; +import ClientName from './ClientName'; +import ClientPhone from './ClientPhone'; +import ClientPosition from './ClientPosition'; +import ClientRepository from './ClientRepository'; +import ClientSaver from './save/ClientSaver'; + +type CreateClientPrimitives = { + id: string; + name: string; + email: string; + phone: string; + company: string; + position: string; +}; +export default class Client { + constructor( + readonly id: ClientId, + readonly name: ClientName, + readonly email: ClientEmail, + readonly phone: ClientPhone, + readonly company: ClientCompany, + readonly position: ClientPosition + ) {} + + public static create(_: CreateClientPrimitives): Client { + return new Client( + new ClientId(_.id), + new ClientName(_.name), + new ClientEmail(_.email), + new ClientPhone(_.phone), + new ClientCompany(_.company), + new ClientPosition(_.position) + ); + } + + public static fromPrimitives(_: CreateClientPrimitives): Client { + return new Client( + new ClientId(_.id), + new ClientName(_.name), + new ClientEmail(_.email), + new ClientPhone(_.phone), + new ClientCompany(_.company), + new ClientPosition(_.position) + ); + } + + static save(command: RegisterClientCommand) { + return async (repository: ClientRepository): Promise => { + await ClientSaver.save(command, repository); + }; + } +} diff --git a/src/contexts/backoffice/clients/domain/ClientCompany.ts b/src/contexts/backoffice/clients/domain/ClientCompany.ts new file mode 100644 index 0000000..487215d --- /dev/null +++ b/src/contexts/backoffice/clients/domain/ClientCompany.ts @@ -0,0 +1,3 @@ +import { StringValueObject } from '../../../shared/domain/value-object/StringValueObject'; + +export default class ClientCompany extends StringValueObject {} diff --git a/src/contexts/backoffice/clients/domain/ClientEmail.ts b/src/contexts/backoffice/clients/domain/ClientEmail.ts new file mode 100644 index 0000000..ae7ebc0 --- /dev/null +++ b/src/contexts/backoffice/clients/domain/ClientEmail.ts @@ -0,0 +1,3 @@ +import { StringValueObject } from '../../../shared/domain/value-object/StringValueObject'; + +export default class ClientEmail extends StringValueObject {} diff --git a/src/contexts/backoffice/clients/domain/ClientId.ts b/src/contexts/backoffice/clients/domain/ClientId.ts new file mode 100644 index 0000000..f20872f --- /dev/null +++ b/src/contexts/backoffice/clients/domain/ClientId.ts @@ -0,0 +1,3 @@ +import { UuidValueObject } from '../../../shared/domain/value-object/UuidValueObject'; + +export default class ClientId extends UuidValueObject {} diff --git a/src/contexts/backoffice/clients/domain/ClientName.ts b/src/contexts/backoffice/clients/domain/ClientName.ts new file mode 100644 index 0000000..eeba0af --- /dev/null +++ b/src/contexts/backoffice/clients/domain/ClientName.ts @@ -0,0 +1,3 @@ +import { StringValueObject } from '../../../shared/domain/value-object/StringValueObject'; + +export default class ClientName extends StringValueObject {} diff --git a/src/contexts/backoffice/clients/domain/ClientPhone.ts b/src/contexts/backoffice/clients/domain/ClientPhone.ts new file mode 100644 index 0000000..59a9a76 --- /dev/null +++ b/src/contexts/backoffice/clients/domain/ClientPhone.ts @@ -0,0 +1,3 @@ +import { StringValueObject } from '../../../shared/domain/value-object/StringValueObject'; + +export default class ClientPhone extends StringValueObject {} diff --git a/src/contexts/backoffice/clients/domain/ClientPosition.ts b/src/contexts/backoffice/clients/domain/ClientPosition.ts new file mode 100644 index 0000000..70ca733 --- /dev/null +++ b/src/contexts/backoffice/clients/domain/ClientPosition.ts @@ -0,0 +1,3 @@ +import { StringValueObject } from '../../../shared/domain/value-object/StringValueObject'; + +export default class ClientPosition extends StringValueObject {} diff --git a/src/contexts/backoffice/clients/domain/ClientRepository.ts b/src/contexts/backoffice/clients/domain/ClientRepository.ts new file mode 100644 index 0000000..c4eb70e --- /dev/null +++ b/src/contexts/backoffice/clients/domain/ClientRepository.ts @@ -0,0 +1,5 @@ +import Client from './Client'; + +export default interface ClientRepository { + save(_: Client): Promise; +} diff --git a/src/contexts/backoffice/clients/domain/save/ClientSaver.ts b/src/contexts/backoffice/clients/domain/save/ClientSaver.ts new file mode 100644 index 0000000..c3af4b2 --- /dev/null +++ b/src/contexts/backoffice/clients/domain/save/ClientSaver.ts @@ -0,0 +1,18 @@ +import RegisterClientCommand from '../../application/register/RegisterClientCommand'; +import Client from '../Client'; +import ClientRepository from '../ClientRepository'; + +export default class ClientSaver { + static async save(command: RegisterClientCommand, repository: ClientRepository): Promise { + const { data } = command; + const client = Client.create({ + id: data.id, + name: data.name, + email: data.email, + phone: data.phone, + company: data.company, + position: data.position + }); + await repository.save(client); + } +} diff --git a/tests/contexts/backoffice/clients/application/register/ClientRegistrar.test.ts b/tests/contexts/backoffice/clients/application/register/ClientRegistrar.test.ts new file mode 100644 index 0000000..dd303bc --- /dev/null +++ b/tests/contexts/backoffice/clients/application/register/ClientRegistrar.test.ts @@ -0,0 +1,21 @@ +import ClientRegistrar from '../../../../../../src/contexts/backoffice/clients/application/register/ClientRegistrar'; +import RegisterClientCommandHandler from '../../../../../../src/contexts/backoffice/clients/application/register/RegisterClientCommandHandler'; +import { ClientMother } from '../../domain/ClientMother'; +import MockClientRepository from '../../domain/MockClientRepository'; +import { RegisterClientCommandMother } from './RegisterClientCommandMother'; + +describe('ClientRegistrar', () => { + describe('#register', () => { + it('Should register a non existing client', () => { + const command = RegisterClientCommandMother.random(); + const repository = new MockClientRepository(); + const client = ClientMother.fromCommand(command); + const registar = new ClientRegistrar(repository); + const handler = new RegisterClientCommandHandler(registar); + + handler.handle(command); + + repository.ensureSaveHasBeenCalledWith(client); + }); + }); +}); diff --git a/tests/contexts/backoffice/clients/application/register/RegisterClientCommandMother.ts b/tests/contexts/backoffice/clients/application/register/RegisterClientCommandMother.ts new file mode 100644 index 0000000..6b079f8 --- /dev/null +++ b/tests/contexts/backoffice/clients/application/register/RegisterClientCommandMother.ts @@ -0,0 +1,18 @@ +import * as fs from 'faker'; + +import RegisterClientCommand from '../../../../../../src/contexts/backoffice/clients/application/register/RegisterClientCommand'; + +export class RegisterClientCommandMother { + static random(): RegisterClientCommand { + const primitives = { + id: fs.datatype.uuid(), + name: fs.name.firstName(), + email: fs.internet.email(), + phone: fs.phone.phoneNumber(), + company: fs.company.companyName(), + position: fs.name.jobType() + }; + + return new RegisterClientCommand(primitives); + } +} diff --git a/tests/contexts/backoffice/clients/domain/ClientMother.ts b/tests/contexts/backoffice/clients/domain/ClientMother.ts new file mode 100644 index 0000000..d5b820f --- /dev/null +++ b/tests/contexts/backoffice/clients/domain/ClientMother.ts @@ -0,0 +1,17 @@ +import RegisterClientCommand from '../../../../../src/contexts/backoffice/clients/application/register/RegisterClientCommand'; +import Client from '../../../../../src/contexts/backoffice/clients/domain/Client'; + +export class ClientMother { + static fromCommand(_: RegisterClientCommand): Client { + const { data } = _; + + return Client.fromPrimitives({ + id: data.id, + name: data.name, + email: data.email, + phone: data.phone, + company: data.company, + position: data.position + }); + } +} diff --git a/tests/contexts/backoffice/clients/domain/MockClientRepository.ts b/tests/contexts/backoffice/clients/domain/MockClientRepository.ts new file mode 100644 index 0000000..295216e --- /dev/null +++ b/tests/contexts/backoffice/clients/domain/MockClientRepository.ts @@ -0,0 +1,19 @@ +import Client from '../../../../../src/contexts/backoffice/clients/domain/Client'; +import ClientRepository from '../../../../../src/contexts/backoffice/clients/domain/ClientRepository'; + +export default class MockClientRepository implements ClientRepository { + private readonly mockSave: jest.Mock; + constructor() { + this.mockSave = jest.fn(); + } + + async save(_: Client): Promise { + this.mockSave(_); + + return Promise.resolve(); + } + + ensureSaveHasBeenCalledWith(client: Client): void { + expect(this.mockSave).toHaveBeenCalledWith(client); + } +}