Skip to content

Commit

Permalink
Add signupUserCommandHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
mapeveri committed Dec 6, 2024
1 parent 82ae567 commit 2a2bbaf
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/languages/_dependencyInjection/commandHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import CreateWordCommandHandler from '@src/languages/application/term/command/wo
import AddLikeTermCommandHandler from '@src/languages/application/term/command/addLikeTermCommandHandler';
import DislikeTermCommandHandler from '@src/languages/application/term/command/dislikeTermCommandHandler';
import UpdateWordCommandHandler from '@src/languages/application/term/command/word/updateWordCommandHandler';
import SignupUserCommandHandler from '@src/languages/application/auth/command/signupUserCommandHandler';

export const commands = [
LoginUserCommandHandler,
SignupUserCommandHandler,
CreateCountryCommandHandler,
CreateExpressionCommandHandler,
CreateUserCommandHandler,
Expand Down
35 changes: 35 additions & 0 deletions src/languages/application/auth/command/signupUserCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { CommandHandler, ICommandHandler } from '@src/shared/domain/bus/commandBus/commandHandler';
import SignupUserCommand from '@src/languages/application/auth/command/signupUserCommand';
import { Inject } from '@src/shared/domain/injector/inject.decorator';
import UserRepository, { USER_REPOSITORY } from '@src/languages/domain/user/userRepository';
import { EVENT_BUS, EventBus } from '@src/shared/domain/bus/eventBus/eventBus';
import UserId from '@src/languages/domain/user/userId';
import UserAlreadyExistsException from '@src/languages/domain/user/userAlreadyExistsException';
import User from '@src/languages/domain/user/user';
import Email from '@src/shared/domain/valueObjects/email';

@CommandHandler(SignupUserCommand)
export default class SignupUserCommandHandler implements ICommandHandler<SignupUserCommand> {
constructor(
@Inject(USER_REPOSITORY) private readonly userRepository: UserRepository,
@Inject(EVENT_BUS) private readonly eventBus: EventBus,
) {}

async execute(command: SignupUserCommand): Promise<void> {
const userId = UserId.of(command.id);
await this.guardUserDoesNotExists(userId);

const user = User.create(userId, command.name, command.provider, Email.of(command.email), command.photo);

this.userRepository.save(user);

void this.eventBus.publish(user.pullDomainEvents());
}

private async guardUserDoesNotExists(userId: UserId): Promise<void> {
const user = await this.userRepository.findById(userId);
if (user) {
throw new UserAlreadyExistsException(userId.value);
}
}
}
7 changes: 6 additions & 1 deletion src/languages/domain/user/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AggregateRoot } from '@src/shared/domain/aggregate/aggregateRoot';
import UserId from './userId';
import Email from '@src/shared/domain/valueObjects/email';
import UserUpdatedEvent from '@src/languages/domain/user/userUpdatedEvent';
import UserCreatedEvent from '@src/languages/domain/user/userCreatedEvent';

export type UserPrimitives = {
id: string;
Expand Down Expand Up @@ -37,7 +38,11 @@ export default class User extends AggregateRoot {
}

static create(id: UserId, name: string, provider: string, email: Email, photo: string): User {
return new this(id, name, provider, email, photo, []);
const user = new this(id, name, provider, email, photo, []);

user.record(new UserCreatedEvent(id.toString(), name, provider, email.toString(), photo));

return user;
}

update(name: string, photo: string, interests: string[]): void {
Expand Down
37 changes: 37 additions & 0 deletions src/languages/domain/user/userCreatedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { DomainEvent } from '@src/shared/domain/bus/eventBus/domainEvent';

export default class UserCreatedEvent extends DomainEvent {
constructor(
public readonly id: string,
public readonly name: string,
public readonly provider: string,
public readonly email: string,
public readonly photo: string,
eventId = '',
) {
super(id, eventId);
}

public static fromPrimitives(payload: { [key: string]: any }): DomainEvent {
return new this(
payload['id'],
payload['name'],
payload['provider'],
payload['email'],
payload['photo'],
payload['eventId'],
);
}

public static eventTypeName(): string {
return 'user.created';
}

public classPathName(): string {
return 'languages.domain.user.userCreatedEvent';
}

public static aggregateTypeName(): string {
return 'user';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { beforeEach, beforeAll, describe, expect, it, jest } from '@jest/globals';
import { EventBusMock } from '@test/unit/shared/domain/buses/eventBus/eventBusMock';
import SignupUserCommandHandler from '@src/languages/application/auth/command/signupUserCommandHandler';
import { SignupUserCommandMother } from '@test/unit/languages/application/auth/command/signupUserCommandMother';
import SignupUserCommand from '@src/languages/application/auth/command/signupUserCommand';
import { UserRepositoryMock } from '@test/unit/languages/domain/user/userRepositoryMock';
import { UserMother } from '@test/unit/languages/domain/user/userMother';
import UserAlreadyExistsException from '@src/languages/domain/user/userAlreadyExistsException';
import { UserIdMother } from '@test/unit/languages/domain/user/userIdMother';
import UserCreatedEvent from '@src/languages/domain/user/userCreatedEvent';
import { UserCreatedEventMother } from '@test/unit/languages/domain/user/userCreatedEventMother';

describe('Given a SignupUserCommandHandler to handle', () => {
let userRepository: UserRepositoryMock;
let eventBus: EventBusMock;
let handler: SignupUserCommandHandler;

const prepareDependencies = () => {
userRepository = new UserRepositoryMock();
eventBus = new EventBusMock();
};

const initHandler = () => {
handler = new SignupUserCommandHandler(userRepository, eventBus);

jest.useFakeTimers();
};

const clean = () => {
userRepository.clean();
eventBus.clean();
};

beforeAll(() => {
prepareDependencies();
initHandler();
});

beforeEach(() => {
clean();
});

describe('When the user already exists', () => {
let command: SignupUserCommand;

function startScenario() {
command = SignupUserCommandMother.random();
userRepository.add(UserMother.random({ id: UserIdMother.random(command.id) }));
}

beforeEach(startScenario);

it('should raise an exception', async () => {
await expect(handler.execute(command)).rejects.toThrowError(UserAlreadyExistsException);

expect(userRepository.storedChanged()).toBeFalsy();
expect(userRepository.stored()).toHaveLength(0);
});

it('should not publish any events', async () => {
await expect(handler.execute(command)).rejects.toThrowError(UserAlreadyExistsException);

expect(eventBus.domainEvents()).toHaveLength(0);
});
});

describe('When the user does not exists', () => {
let data: { id: string; email: string; provider: string; photo: string; name: string };
let command: SignupUserCommand;

function startScenario() {
data = {
id: '4a4df157-8ab8-50af-bb39-88e8ce29eb16',
email: '[email protected]',
provider: 'google',
photo: '',
name: 'test',
};
command = SignupUserCommandMother.random(data);
}

beforeEach(startScenario);

it('should create the user', async () => {
await handler.execute(command);

expect(userRepository.storedChanged()).toBeTruthy();
expect(userRepository.stored()).toHaveLength(1);
const user = userRepository.stored()[0];
expect(user.toPrimitives()).toEqual({ ...data, interests: [] });
});

it('should publish the events', async () => {
const event = UserCreatedEventMother.createFromSignupUserCommand(command);
await handler.execute(command);

expect(eventBus.domainEvents()).toHaveLength(1);
expect(eventBus.domainEvents()[0]).toBeInstanceOf(UserCreatedEvent);
expect(eventBus.domainEvents()[0]).toEqual({
...event,
});
});
});
});
10 changes: 10 additions & 0 deletions test/unit/languages/domain/user/userCreatedEventMother.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect } from '@jest/globals';
import SignupUserCommand from '@src/languages/application/auth/command/signupUserCommand';
import UserCreatedEvent from '@src/languages/domain/user/userCreatedEvent';

export class UserCreatedEventMother {
static createFromSignupUserCommand(command: SignupUserCommand): UserCreatedEvent {
const eventId = expect.any(String) as unknown as string;
return new UserCreatedEvent(command.id, command.name, command.provider, command.email, command.photo, eventId);
}
}

0 comments on commit 2a2bbaf

Please sign in to comment.