user update
This commit is contained in:
parent
d5c2bb396d
commit
52fd0b952b
|
@ -21,6 +21,7 @@
|
||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
"@nestjs/cqrs": "^9.0.1",
|
"@nestjs/cqrs": "^9.0.1",
|
||||||
|
"@nestjs/event-emitter": "^2.0.0",
|
||||||
"@nestjs/microservices": "^9.2.1",
|
"@nestjs/microservices": "^9.2.1",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
"@nestjs/terminus": "^9.2.2",
|
"@nestjs/terminus": "^9.2.2",
|
||||||
|
@ -1937,6 +1938,19 @@
|
||||||
"@nestjs/common": "^9.4.2"
|
"@nestjs/common": "^9.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mobicoop/ddd-library/node_modules/@nestjs/event-emitter": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-5mskPMS4KVH6LghC+NynfdmGiMCOOv9CdgVpuWGipLrJECv5KWc7vaW5o/9BYrcqPkN7Ted6CJ+O4AfsTiRlgw==",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter2": "6.4.9"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0",
|
||||||
|
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0",
|
||||||
|
"reflect-metadata": "^0.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mobicoop/health-module": {
|
"node_modules/@mobicoop/health-module": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mobicoop/health-module/-/health-module-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mobicoop/health-module/-/health-module-2.0.0.tgz",
|
||||||
|
@ -2206,15 +2220,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/event-emitter": {
|
"node_modules/@nestjs/event-emitter": {
|
||||||
"version": "1.4.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.0.tgz",
|
||||||
"integrity": "sha512-5mskPMS4KVH6LghC+NynfdmGiMCOOv9CdgVpuWGipLrJECv5KWc7vaW5o/9BYrcqPkN7Ted6CJ+O4AfsTiRlgw==",
|
"integrity": "sha512-fZRv3+PmqXcbqCDRXRWhKDa+v3gmPUq4x5sQE5reVlDtEaCoAXwtGrtNswPtqd0msjyo8OWZF9k1sFjeRL6Xag==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eventemitter2": "6.4.9"
|
"eventemitter2": "6.4.9"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0",
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0",
|
"@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
"reflect-metadata": "^0.1.12"
|
"reflect-metadata": "^0.1.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
"@nestjs/cqrs": "^9.0.1",
|
"@nestjs/cqrs": "^9.0.1",
|
||||||
|
"@nestjs/event-emitter": "^2.0.0",
|
||||||
"@nestjs/microservices": "^9.2.1",
|
"@nestjs/microservices": "^9.2.1",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
"@nestjs/terminus": "^9.2.2",
|
"@nestjs/terminus": "^9.2.2",
|
||||||
|
|
|
@ -14,10 +14,12 @@ import { MessagerModule } from './modules/messager/messager.module';
|
||||||
import { USER_REPOSITORY } from './modules/user/user.di-tokens';
|
import { USER_REPOSITORY } from './modules/user/user.di-tokens';
|
||||||
import { MESSAGE_PUBLISHER } from './modules/messager/messager.di-tokens';
|
import { MESSAGE_PUBLISHER } from './modules/messager/messager.di-tokens';
|
||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
ConfigurationModule.forRootAsync({
|
ConfigurationModule.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
import {
|
||||||
|
AggregateID,
|
||||||
|
ConflictException,
|
||||||
|
UniqueConstraintException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
import { CreateUserCommand } from './create-user.command';
|
import { CreateUserCommand } from './create-user.command';
|
||||||
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||||
import { UserRepositoryPort } from '../../ports/user.repository.port';
|
import { UserRepositoryPort } from '../../ports/user.repository.port';
|
||||||
import { UserEntity } from '../../../domain/user.entity';
|
import { UserEntity } from '../../../domain/user.entity';
|
||||||
import { UserAlreadyExistsException } from '../../../domain/user.errors';
|
import {
|
||||||
|
EmailAlreadyExistsException,
|
||||||
|
PhoneAlreadyExistsException,
|
||||||
|
UserAlreadyExistsException,
|
||||||
|
} from '../../../domain/user.errors';
|
||||||
|
|
||||||
@CommandHandler(CreateUserCommand)
|
@CommandHandler(CreateUserCommand)
|
||||||
export class CreateUserService implements ICommandHandler {
|
export class CreateUserService implements ICommandHandler {
|
||||||
|
@ -29,6 +37,18 @@ export class CreateUserService implements ICommandHandler {
|
||||||
if (error instanceof ConflictException) {
|
if (error instanceof ConflictException) {
|
||||||
throw new UserAlreadyExistsException(error);
|
throw new UserAlreadyExistsException(error);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
error instanceof UniqueConstraintException &&
|
||||||
|
error.message.includes('email')
|
||||||
|
) {
|
||||||
|
throw new EmailAlreadyExistsException(error);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
error instanceof UniqueConstraintException &&
|
||||||
|
error.message.includes('phone')
|
||||||
|
) {
|
||||||
|
throw new PhoneAlreadyExistsException(error);
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import {
|
import { AggregateID, UniqueConstraintException } from '@mobicoop/ddd-library';
|
||||||
AggregateID,
|
|
||||||
ConflictException,
|
|
||||||
UniqueConstraintException,
|
|
||||||
} from '@mobicoop/ddd-library';
|
|
||||||
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||||
import { UserRepositoryPort } from '../../ports/user.repository.port';
|
import { UserRepositoryPort } from '../../ports/user.repository.port';
|
||||||
import { UserEntity } from '../../../domain/user.entity';
|
import { UserEntity } from '../../../domain/user.entity';
|
||||||
|
@ -12,7 +8,6 @@ import { UpdateUserCommand } from './update-user.command';
|
||||||
import {
|
import {
|
||||||
EmailAlreadyExistsException,
|
EmailAlreadyExistsException,
|
||||||
PhoneAlreadyExistsException,
|
PhoneAlreadyExistsException,
|
||||||
UserAlreadyExistsException,
|
|
||||||
} from '@modules/user/core/domain/user.errors';
|
} from '@modules/user/core/domain/user.errors';
|
||||||
|
|
||||||
@CommandHandler(UpdateUserCommand)
|
@CommandHandler(UpdateUserCommand)
|
||||||
|
@ -36,9 +31,6 @@ export class UpdateUserService implements ICommandHandler {
|
||||||
await this.userRepository.update(user.id, user);
|
await this.userRepository.update(user.id, user);
|
||||||
return user.id;
|
return user.id;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error instanceof ConflictException) {
|
|
||||||
throw new UserAlreadyExistsException(error);
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
error instanceof UniqueConstraintException &&
|
error instanceof UniqueConstraintException &&
|
||||||
error.message.includes('email')
|
error.message.includes('email')
|
||||||
|
|
|
@ -25,10 +25,10 @@ export class UserEntity extends AggregateRoot<UserProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
update(props: UpdateUserProps): void {
|
update(props: UpdateUserProps): void {
|
||||||
this.props.firstName = props.firstName;
|
this.props.firstName = props.firstName ?? this.props.firstName;
|
||||||
this.props.lastName = props.lastName;
|
this.props.lastName = props.lastName ?? this.props.lastName;
|
||||||
this.props.email = props.email;
|
this.props.email = props.email ?? this.props.email;
|
||||||
this.props.phone = props.phone;
|
this.props.phone = props.phone ?? this.props.phone;
|
||||||
this.addEvent(
|
this.addEvent(
|
||||||
new UserUpdatedDomainEvent({
|
new UserUpdatedDomainEvent({
|
||||||
aggregateId: this._id,
|
aggregateId: this._id,
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
// All properties that a User has
|
// All properties that a User has
|
||||||
export interface UserProps {
|
export interface UserProps {
|
||||||
firstName: string;
|
firstName?: string;
|
||||||
lastName: string;
|
lastName?: string;
|
||||||
email: string;
|
email?: string;
|
||||||
phone: string;
|
phone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties that are needed for a User creation
|
// Properties that are needed for a User creation
|
||||||
export interface CreateUserProps {
|
export interface CreateUserProps {
|
||||||
firstName: string;
|
firstName?: string;
|
||||||
lastName: string;
|
lastName?: string;
|
||||||
email: string;
|
email?: string;
|
||||||
phone: string;
|
phone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateUserProps {
|
export interface UpdateUserProps {
|
||||||
firstName: string;
|
firstName?: string;
|
||||||
lastName: string;
|
lastName?: string;
|
||||||
email: string;
|
email?: string;
|
||||||
phone: string;
|
phone?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,11 @@ import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||||
import { CreateUserRequestDto } from './dtos/create-user.request.dto';
|
import { CreateUserRequestDto } from './dtos/create-user.request.dto';
|
||||||
import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
|
import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
|
||||||
import { UserAlreadyExistsException } from '@modules/user/core/domain/user.errors';
|
import {
|
||||||
|
EmailAlreadyExistsException,
|
||||||
|
PhoneAlreadyExistsException,
|
||||||
|
UserAlreadyExistsException,
|
||||||
|
} from '@modules/user/core/domain/user.errors';
|
||||||
|
|
||||||
@UsePipes(
|
@UsePipes(
|
||||||
new RpcValidationPipe({
|
new RpcValidationPipe({
|
||||||
|
@ -27,7 +31,11 @@ export class CreateUserGrpcController {
|
||||||
);
|
);
|
||||||
return new IdResponse(aggregateID);
|
return new IdResponse(aggregateID);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error instanceof UserAlreadyExistsException)
|
if (
|
||||||
|
error instanceof UserAlreadyExistsException ||
|
||||||
|
error instanceof EmailAlreadyExistsException ||
|
||||||
|
error instanceof PhoneAlreadyExistsException
|
||||||
|
)
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: RpcExceptionCode.ALREADY_EXISTS,
|
code: RpcExceptionCode.ALREADY_EXISTS,
|
||||||
message: error.message,
|
message: error.message,
|
|
@ -3,19 +3,19 @@ syntax = "proto3";
|
||||||
package user;
|
package user;
|
||||||
|
|
||||||
service UserService {
|
service UserService {
|
||||||
rpc FindOneByUuid(UserByUuid) returns (User);
|
rpc FindOneById(UserById) returns (User);
|
||||||
rpc FindAll(UserFilter) returns (Users);
|
rpc FindAll(UserFilter) returns (Users);
|
||||||
rpc Create(User) returns (User);
|
rpc Create(User) returns (UserById);
|
||||||
rpc Update(User) returns (User);
|
rpc Update(User) returns (UserById);
|
||||||
rpc Delete(UserByUuid) returns (Empty);
|
rpc Delete(UserById) returns (Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserByUuid {
|
message UserById {
|
||||||
string uuid = 1;
|
string id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message User {
|
message User {
|
||||||
string uuid = 1;
|
string id = 1;
|
||||||
string firstName = 2;
|
string firstName = 2;
|
||||||
string lastName = 3;
|
string lastName = 3;
|
||||||
string email = 4;
|
string email = 4;
|
|
@ -1,12 +1,16 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { AggregateID } from '@mobicoop/ddd-library';
|
import { AggregateID, UniqueConstraintException } from '@mobicoop/ddd-library';
|
||||||
import { ConflictException } from '@mobicoop/ddd-library';
|
import { ConflictException } from '@mobicoop/ddd-library';
|
||||||
import { CreateUserRequestDto } from '@modules/user/interface/dtos/grpc-controllers/dtos/create-user.request.dto';
|
import { CreateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/create-user.request.dto';
|
||||||
import { CreateUserService } from '@modules/user/core/application/commands/create-user/create-user.service';
|
import { CreateUserService } from '@modules/user/core/application/commands/create-user/create-user.service';
|
||||||
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||||
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
||||||
import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
|
import { CreateUserCommand } from '@modules/user/core/application/commands/create-user/create-user.command';
|
||||||
import { UserAlreadyExistsException } from '@modules/user/core/domain/user.errors';
|
import {
|
||||||
|
EmailAlreadyExistsException,
|
||||||
|
PhoneAlreadyExistsException,
|
||||||
|
UserAlreadyExistsException,
|
||||||
|
} from '@modules/user/core/domain/user.errors';
|
||||||
|
|
||||||
const createUserRequest: CreateUserRequestDto = {
|
const createUserRequest: CreateUserRequestDto = {
|
||||||
firstName: 'John',
|
firstName: 'John',
|
||||||
|
@ -23,7 +27,13 @@ const mockUserRepository = {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
})
|
})
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
throw new ConflictException('already exists');
|
throw new ConflictException('User already exists');
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new UniqueConstraintException('email already exists');
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new UniqueConstraintException('phone already exists');
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,5 +85,21 @@ describe('create-user.service', () => {
|
||||||
createUserService.execute(createUserCommand),
|
createUserService.execute(createUserCommand),
|
||||||
).rejects.toBeInstanceOf(UserAlreadyExistsException);
|
).rejects.toBeInstanceOf(UserAlreadyExistsException);
|
||||||
});
|
});
|
||||||
|
it('should throw an exception if Email already exists', async () => {
|
||||||
|
UserEntity.create = jest.fn().mockReturnValue({
|
||||||
|
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
createUserService.execute(createUserCommand),
|
||||||
|
).rejects.toBeInstanceOf(EmailAlreadyExistsException);
|
||||||
|
});
|
||||||
|
it('should throw an exception if Phone already exists', async () => {
|
||||||
|
UserEntity.create = jest.fn().mockReturnValue({
|
||||||
|
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
createUserService.execute(createUserCommand),
|
||||||
|
).rejects.toBeInstanceOf(PhoneAlreadyExistsException);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import {
|
||||||
|
AggregateID,
|
||||||
|
NotFoundException,
|
||||||
|
UniqueConstraintException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
|
import { USER_REPOSITORY } from '@modules/user/user.di-tokens';
|
||||||
|
import { UserEntity } from '@modules/user/core/domain/user.entity';
|
||||||
|
import {
|
||||||
|
EmailAlreadyExistsException,
|
||||||
|
PhoneAlreadyExistsException,
|
||||||
|
} from '@modules/user/core/domain/user.errors';
|
||||||
|
import { UpdateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/update-user.request.dto';
|
||||||
|
import { UpdateUserService } from '@modules/user/core/application/commands/update-user/update-user.service';
|
||||||
|
import { UpdateUserCommand } from '@modules/user/core/application/commands/update-user/update-user.command';
|
||||||
|
|
||||||
|
const updateFirstNameUserRequest: UpdateUserRequestDto = {
|
||||||
|
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||||
|
firstName: 'Johnny',
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateEmailUserRequest: UpdateUserRequestDto = {
|
||||||
|
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||||
|
email: 'john.doe@already.exists.email.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePhoneUserRequest: UpdateUserRequestDto = {
|
||||||
|
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||||
|
phone: '+33611223344',
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const userToUpdate: UserEntity = new UserEntity({
|
||||||
|
id: 'c97b1783-76cf-4840-b298-b90b13c58894',
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
props: {
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
email: 'john.doe@email.com',
|
||||||
|
phone: '+33611223344',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockUserRepository = {
|
||||||
|
findOneById: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new NotFoundException('Record not found');
|
||||||
|
})
|
||||||
|
.mockImplementation(() => userToUpdate),
|
||||||
|
update: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => 'c97b1783-76cf-4840-b298-b90b13c58894')
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new UniqueConstraintException('email already exists');
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new UniqueConstraintException('phone already exists');
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('update-user.service', () => {
|
||||||
|
let updateUserService: UpdateUserService;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: USER_REPOSITORY,
|
||||||
|
useValue: mockUserRepository,
|
||||||
|
},
|
||||||
|
UpdateUserService,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
updateUserService = module.get<UpdateUserService>(UpdateUserService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(updateUserService).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('execution', () => {
|
||||||
|
it('should throw an exception if use is not found', async () => {
|
||||||
|
const updateUserCommand = new UpdateUserCommand(
|
||||||
|
updateFirstNameUserRequest,
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
updateUserService.execute(updateUserCommand),
|
||||||
|
).rejects.toBeInstanceOf(NotFoundException);
|
||||||
|
});
|
||||||
|
it('should update a user firstName', async () => {
|
||||||
|
jest.spyOn(userToUpdate, 'update');
|
||||||
|
const updateUserCommand = new UpdateUserCommand(
|
||||||
|
updateFirstNameUserRequest,
|
||||||
|
);
|
||||||
|
const result: AggregateID = await updateUserService.execute(
|
||||||
|
updateUserCommand,
|
||||||
|
);
|
||||||
|
expect(result).toBe('c97b1783-76cf-4840-b298-b90b13c58894');
|
||||||
|
expect(userToUpdate.update).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
it('should throw an exception if Email already exists', async () => {
|
||||||
|
const updateUserCommand = new UpdateUserCommand(updateEmailUserRequest);
|
||||||
|
await expect(
|
||||||
|
updateUserService.execute(updateUserCommand),
|
||||||
|
).rejects.toBeInstanceOf(EmailAlreadyExistsException);
|
||||||
|
});
|
||||||
|
it('should throw an exception if Phone already exists', async () => {
|
||||||
|
const updateUserCommand = new UpdateUserCommand(updatePhoneUserRequest);
|
||||||
|
await expect(
|
||||||
|
updateUserService.execute(updateUserCommand),
|
||||||
|
).rejects.toBeInstanceOf(PhoneAlreadyExistsException);
|
||||||
|
});
|
||||||
|
it('should throw an error if something bad happens', async () => {
|
||||||
|
const updateUserCommand = new UpdateUserCommand(updatePhoneUserRequest);
|
||||||
|
await expect(
|
||||||
|
updateUserService.execute(updateUserCommand),
|
||||||
|
).rejects.toBeInstanceOf(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,9 +3,13 @@ import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { RpcException } from '@nestjs/microservices';
|
import { RpcException } from '@nestjs/microservices';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { UserAlreadyExistsException } from '@modules/user/core/domain/user.errors';
|
import {
|
||||||
import { CreateUserGrpcController } from '@modules/user/interface/dtos/grpc-controllers/create-user.grpc.controller';
|
EmailAlreadyExistsException,
|
||||||
import { CreateUserRequestDto } from '@modules/user/interface/dtos/grpc-controllers/dtos/create-user.request.dto';
|
PhoneAlreadyExistsException,
|
||||||
|
UserAlreadyExistsException,
|
||||||
|
} from '@modules/user/core/domain/user.errors';
|
||||||
|
import { CreateUserGrpcController } from '@modules/user/interface/grpc-controllers/create-user.grpc.controller';
|
||||||
|
import { CreateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/create-user.request.dto';
|
||||||
|
|
||||||
const createUserRequest: CreateUserRequestDto = {
|
const createUserRequest: CreateUserRequestDto = {
|
||||||
firstName: 'John',
|
firstName: 'John',
|
||||||
|
@ -21,6 +25,12 @@ const mockCommandBus = {
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
throw new UserAlreadyExistsException();
|
throw new UserAlreadyExistsException();
|
||||||
})
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new EmailAlreadyExistsException();
|
||||||
|
})
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new PhoneAlreadyExistsException();
|
||||||
|
})
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}),
|
}),
|
||||||
|
@ -75,6 +85,30 @@ describe('Create User Grpc Controller', () => {
|
||||||
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw a dedicated RpcException if email already exists', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await createUserGrpcController.create(createUserRequest);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a dedicated RpcException if phone already exists', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
expect.assertions(3);
|
||||||
|
try {
|
||||||
|
await createUserGrpcController.create(createUserRequest);
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw a generic RpcException', async () => {
|
it('should throw a generic RpcException', async () => {
|
||||||
jest.spyOn(mockCommandBus, 'execute');
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
|
|
|
@ -4,8 +4,8 @@ import {
|
||||||
EmailAlreadyExistsException,
|
EmailAlreadyExistsException,
|
||||||
PhoneAlreadyExistsException,
|
PhoneAlreadyExistsException,
|
||||||
} from '@modules/user/core/domain/user.errors';
|
} from '@modules/user/core/domain/user.errors';
|
||||||
import { UpdateUserRequestDto } from '@modules/user/interface/dtos/grpc-controllers/dtos/update-user.request.dto';
|
import { UpdateUserRequestDto } from '@modules/user/interface/grpc-controllers/dtos/update-user.request.dto';
|
||||||
import { UpdateUserGrpcController } from '@modules/user/interface/dtos/grpc-controllers/update-user.grpc.controller';
|
import { UpdateUserGrpcController } from '@modules/user/interface/grpc-controllers/update-user.grpc.controller';
|
||||||
|
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { RpcException } from '@nestjs/microservices';
|
import { RpcException } from '@nestjs/microservices';
|
||||||
|
|
|
@ -1,60 +1,67 @@
|
||||||
import { RedisClientOptions } from '@liaoliaots/nestjs-redis';
|
import { RedisClientOptions } from '@liaoliaots/nestjs-redis';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module, Provider } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
import { redisStore } from 'cache-manager-ioredis-yet';
|
import { redisStore } from 'cache-manager-ioredis-yet';
|
||||||
import { DatabaseModule } from '../database/database.module';
|
|
||||||
import { UserController } from './adapters/primaries/user.controller';
|
|
||||||
import { UsersRepository } from './adapters/secondaries/users.repository';
|
|
||||||
import { CreateUserUseCase } from './domain/usecases/create-user.usecase';
|
|
||||||
import { DeleteUserUseCase } from './domain/usecases/delete-user.usecase';
|
|
||||||
import { FindAllUsersUseCase } from './domain/usecases/find-all-users.usecase';
|
|
||||||
import { FindUserByUuidUseCase } from './domain/usecases/find-user-by-uuid.usecase';
|
|
||||||
import { UpdateUserUseCase } from './domain/usecases/update-user.usecase';
|
|
||||||
import { UserProfile } from './mappers/user.profile';
|
|
||||||
import { CacheModule } from '@nestjs/cache-manager';
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||||
import {
|
import { CreateUserGrpcController } from './interface/grpc-controllers/create-user.grpc.controller';
|
||||||
MESSAGE_BROKER_PUBLISHER,
|
import { UpdateUserGrpcController } from './interface/grpc-controllers/update-user.grpc.controller';
|
||||||
MESSAGE_PUBLISHER,
|
import { CreateUserService } from './core/application/commands/create-user/create-user.service';
|
||||||
} from '../../app.constants';
|
import { UpdateUserService } from './core/application/commands/update-user/update-user.service';
|
||||||
import { MessagePublisher } from './adapters/secondaries/message-publisher';
|
import { USER_MESSAGE_PUBLISHER, USER_REPOSITORY } from './user.di-tokens';
|
||||||
|
import { UserRepository } from './infrastructure/user.repository';
|
||||||
|
import { UserMapper } from './user.mapper';
|
||||||
|
import { PrismaService } from './infrastructure/prisma.service';
|
||||||
|
|
||||||
|
const imports = [
|
||||||
|
CqrsModule,
|
||||||
|
CacheModule.registerAsync<RedisClientOptions>({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
store: await redisStore({
|
||||||
|
host: configService.get<string>('REDIS_HOST'),
|
||||||
|
port: configService.get<number>('REDIS_PORT'),
|
||||||
|
password: configService.get<string>('REDIS_PASSWORD'),
|
||||||
|
ttl: configService.get('CACHE_TTL'),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const grpcControllers = [CreateUserGrpcController, UpdateUserGrpcController];
|
||||||
|
|
||||||
|
const commandHandlers: Provider[] = [CreateUserService, UpdateUserService];
|
||||||
|
|
||||||
|
const mappers: Provider[] = [UserMapper];
|
||||||
|
|
||||||
|
const repositories: Provider[] = [
|
||||||
|
{
|
||||||
|
provide: USER_REPOSITORY,
|
||||||
|
useClass: UserRepository,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const messagePublishers: Provider[] = [
|
||||||
|
{
|
||||||
|
provide: USER_MESSAGE_PUBLISHER,
|
||||||
|
useExisting: MessageBrokerPublisher,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const orms: Provider[] = [PrismaService];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports,
|
||||||
DatabaseModule,
|
controllers: [...grpcControllers],
|
||||||
CqrsModule,
|
|
||||||
CacheModule.registerAsync<RedisClientOptions>({
|
|
||||||
imports: [ConfigModule],
|
|
||||||
useFactory: async (configService: ConfigService) => ({
|
|
||||||
store: await redisStore({
|
|
||||||
host: configService.get<string>('REDIS_HOST'),
|
|
||||||
port: configService.get<number>('REDIS_PORT'),
|
|
||||||
password: configService.get<string>('REDIS_PASSWORD'),
|
|
||||||
ttl: configService.get('CACHE_TTL'),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
inject: [ConfigService],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
controllers: [UserController],
|
|
||||||
providers: [
|
providers: [
|
||||||
UserProfile,
|
...commandHandlers,
|
||||||
UsersRepository,
|
...mappers,
|
||||||
FindAllUsersUseCase,
|
...repositories,
|
||||||
FindUserByUuidUseCase,
|
...messagePublishers,
|
||||||
CreateUserUseCase,
|
...orms,
|
||||||
UpdateUserUseCase,
|
|
||||||
DeleteUserUseCase,
|
|
||||||
{
|
|
||||||
provide: MESSAGE_BROKER_PUBLISHER,
|
|
||||||
useClass: MessageBrokerPublisher,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: MESSAGE_PUBLISHER,
|
|
||||||
useClass: MessagePublisher,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [PrismaService, UserMapper, USER_REPOSITORY],
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
||||||
|
|
Loading…
Reference in New Issue