From 9c91aa17cbcb351d352e58dd82a6564e2fb40deb Mon Sep 17 00:00:00 2001 From: Jakson Xavier Date: Thu, 11 May 2023 15:36:12 -0300 Subject: [PATCH] chore: improves example codes --- .../use-cases/example-use-case.spec.ts | 18 ------ src/application/use-cases/example-use-case.ts | 16 ------ src/application/use-cases/user-case.module.ts | 12 ++++ .../errors/user-by-email-not-found.error.ts | 8 +++ .../user/get-user-by-email.use-case.spec.ts | 50 ++++++++++++++++ .../user/get-user-by-email.use-case.ts | 37 ++++++++++++ src/config/database.ts | 4 +- src/core/logic/Replace.ts | 14 +++++ src/domain/entities/.gitkeep | 0 src/domain/entities/user.entity.ts | 37 ++++++++++++ src/domain/value-objects/email.ts | 27 +++++++++ .../errors/email-bad-formatted-error.ts | 8 +++ src/infra/database/database.module.ts | 12 +++- src/infra/database/prisma/mappers/.gitkeep | 0 .../database/prisma/mappers/user.mapper.ts | 22 +++++++ .../database/prisma/repositories/.gitkeep | 0 .../repositories/prisma-users-repository.ts | 37 ++++++++++++ src/infra/database/repositories/.gitkeep | 0 .../database/repositories/users.repository.ts | 11 ++++ src/infra/http/graphql/dto/models/.gitkeep | 0 .../http/graphql/dto/models/user.model.ts | 19 +++++++ .../resolvers/example.resolver.e2e-spec.ts | 36 ------------ .../graphql/resolvers/example.resolver.ts | 9 --- .../resolvers/user.resolver.e2e-spec.ts | 57 +++++++++++++++++++ .../http/graphql/resolvers/user.resolver.ts | 23 ++++++++ .../graphql/view-models/user.view-model.ts | 13 +++++ src/infra/http/http.module.ts | 12 ++-- test/factories/.gitkeep | 0 test/factories/users.factory.ts | 48 ++++++++++++++++ test/repositories/.gitkeep | 0 .../in-memory-users.repository.ts | 25 ++++++++ 31 files changed, 467 insertions(+), 88 deletions(-) delete mode 100644 src/application/use-cases/example-use-case.spec.ts delete mode 100644 src/application/use-cases/example-use-case.ts create mode 100644 src/application/use-cases/user-case.module.ts create mode 100644 src/application/use-cases/user/errors/user-by-email-not-found.error.ts create mode 100644 src/application/use-cases/user/get-user-by-email.use-case.spec.ts create mode 100644 src/application/use-cases/user/get-user-by-email.use-case.ts create mode 100644 src/core/logic/Replace.ts delete mode 100644 src/domain/entities/.gitkeep create mode 100644 src/domain/entities/user.entity.ts create mode 100644 src/domain/value-objects/email.ts create mode 100644 src/domain/value-objects/errors/email-bad-formatted-error.ts delete mode 100644 src/infra/database/prisma/mappers/.gitkeep create mode 100644 src/infra/database/prisma/mappers/user.mapper.ts delete mode 100644 src/infra/database/prisma/repositories/.gitkeep create mode 100644 src/infra/database/prisma/repositories/prisma-users-repository.ts delete mode 100644 src/infra/database/repositories/.gitkeep create mode 100644 src/infra/database/repositories/users.repository.ts delete mode 100644 src/infra/http/graphql/dto/models/.gitkeep create mode 100644 src/infra/http/graphql/dto/models/user.model.ts delete mode 100644 src/infra/http/graphql/resolvers/example.resolver.e2e-spec.ts delete mode 100644 src/infra/http/graphql/resolvers/example.resolver.ts create mode 100644 src/infra/http/graphql/resolvers/user.resolver.e2e-spec.ts create mode 100644 src/infra/http/graphql/resolvers/user.resolver.ts create mode 100644 src/infra/http/graphql/view-models/user.view-model.ts delete mode 100644 test/factories/.gitkeep create mode 100644 test/factories/users.factory.ts delete mode 100644 test/repositories/.gitkeep create mode 100644 test/repositories/in-memory-users.repository.ts diff --git a/src/application/use-cases/example-use-case.spec.ts b/src/application/use-cases/example-use-case.spec.ts deleted file mode 100644 index d26914b..0000000 --- a/src/application/use-cases/example-use-case.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ExampleUseCase } from '@application/use-cases/example-use-case'; - -describe('Example UseCase', () => { - let exampleUseCase: ExampleUseCase; - - beforeEach(() => { - exampleUseCase = new ExampleUseCase(); - }); - - it('should be able to run example use case', async () => { - const response = await exampleUseCase.handle({ - number: 5, - }); - - expect(response).not.toBeNaN(); - expect(response).toBe(10); - }); -}); diff --git a/src/application/use-cases/example-use-case.ts b/src/application/use-cases/example-use-case.ts deleted file mode 100644 index 22c6337..0000000 --- a/src/application/use-cases/example-use-case.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -type ExampleRequest = { - number: number; -}; - -type ExampleResponse = number; - -@Injectable() -export class ExampleUseCase { - // constructor() {} - - async handle({ number }: ExampleRequest): Promise { - return number * 2; - } -} diff --git a/src/application/use-cases/user-case.module.ts b/src/application/use-cases/user-case.module.ts new file mode 100644 index 0000000..07e2ce0 --- /dev/null +++ b/src/application/use-cases/user-case.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; + +import { DatabaseModule } from '@infra/database/database.module'; + +import { GetUserByEmailUseCase } from './user/get-user-by-email.use-case'; + +@Module({ + imports: [DatabaseModule], + providers: [GetUserByEmailUseCase], + exports: [GetUserByEmailUseCase], +}) +export class UseCasesModule {} diff --git a/src/application/use-cases/user/errors/user-by-email-not-found.error.ts b/src/application/use-cases/user/errors/user-by-email-not-found.error.ts new file mode 100644 index 0000000..4c0b241 --- /dev/null +++ b/src/application/use-cases/user/errors/user-by-email-not-found.error.ts @@ -0,0 +1,8 @@ +import { DomainError } from '@core/domain/errors/DomainError'; + +export class UserByEmailNotFoundError extends Error implements DomainError { + constructor(email: string) { + super(`User with email '${email}' was not found.`); + this.name = 'UserNotFound'; + } +} diff --git a/src/application/use-cases/user/get-user-by-email.use-case.spec.ts b/src/application/use-cases/user/get-user-by-email.use-case.spec.ts new file mode 100644 index 0000000..5fae8a5 --- /dev/null +++ b/src/application/use-cases/user/get-user-by-email.use-case.spec.ts @@ -0,0 +1,50 @@ +import { EmailBadFormattedError } from '@domain/value-objects/errors/email-bad-formatted-error'; + +import { UsersRepository } from '@infra/database/repositories/users.repository'; + +import { makeFakeUser } from '@test/factories/users.factory'; +import { InMemoryUsersRepository } from '@test/repositories/in-memory-users.repository'; + +import { UserByEmailNotFoundError } from './errors/user-by-email-not-found.error'; +import { GetUserByEmailUseCase } from './get-user-by-email.use-case'; + +describe('GetUserByEmailUseCase', () => { + let usersRepository: UsersRepository; + + let getUserByEmailUseCase: GetUserByEmailUseCase; + + beforeEach(() => { + usersRepository = new InMemoryUsersRepository(); + + getUserByEmailUseCase = new GetUserByEmailUseCase(usersRepository); + }); + + it('should be able to get user by email', async () => { + const user = makeFakeUser(); + + await usersRepository.create(user); + + const output = await getUserByEmailUseCase.handle(user.email); + + expect(output.isRight()).toBeTruthy(); + expect(output.value).toEqual(user); + }); + + it('should be able an error is returned in case an invalid email address is provided', async () => { + const invalidEmail = 'invalid_email'; + + const output = await getUserByEmailUseCase.handle(invalidEmail); + + expect(output.isLeft()).toBeTruthy(); + expect(output.value).toBeInstanceOf(EmailBadFormattedError); + }); + + it('should be able to return user not found error', async () => { + const email = 'oi@rocketseat.com.br'; + + const output = await getUserByEmailUseCase.handle(email); + + expect(output.isLeft()).toBeTruthy(); + expect(output.value).toBeInstanceOf(UserByEmailNotFoundError); + }); +}); diff --git a/src/application/use-cases/user/get-user-by-email.use-case.ts b/src/application/use-cases/user/get-user-by-email.use-case.ts new file mode 100644 index 0000000..6ab0c4a --- /dev/null +++ b/src/application/use-cases/user/get-user-by-email.use-case.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common'; + +import { Either, left, right } from '@core/logic/Either'; + +import { User } from '@domain/entities/user.entity'; +import { Email } from '@domain/value-objects/email'; +import { EmailBadFormattedError } from '@domain/value-objects/errors/email-bad-formatted-error'; + +import { UsersRepository } from '@infra/database/repositories/users.repository'; + +import { UserByEmailNotFoundError } from './errors/user-by-email-not-found.error'; + +type GetUserByEmailResponse = Either< + EmailBadFormattedError | UserByEmailNotFoundError, + User +>; + +@Injectable() +export class GetUserByEmailUseCase { + constructor(private readonly usersRepository: UsersRepository) {} + + async handle(email: string): Promise { + const isInvalidEmail = !Email.validate(email); + + if (isInvalidEmail) { + return left(new EmailBadFormattedError(email)); + } + + const user = await this.usersRepository.findByEmail(email); + + if (!user) { + return left(new UserByEmailNotFoundError(email)); + } + + return right(user); + } +} diff --git a/src/config/database.ts b/src/config/database.ts index 04bf179..003f7cb 100644 --- a/src/config/database.ts +++ b/src/config/database.ts @@ -1,7 +1,7 @@ import 'dotenv/config'; -export const USER = process.env.DATABASE_USER ?? 'docker'; -export const PASSWORD = process.env.DATABASE_PASS ?? 'docker'; +export const USER = process.env.DATABASE_USER ?? 'root'; +export const PASSWORD = process.env.DATABASE_PASS ?? 'toor'; export const HOST = process.env.DATABASE_HOST ?? 'localhost'; export const PORT = process.env.DATABASE_PORT ?? '3306'; export const NAME = process.env.DATABASE_NAME ?? 'app'; diff --git a/src/core/logic/Replace.ts b/src/core/logic/Replace.ts new file mode 100644 index 0000000..5a48cf3 --- /dev/null +++ b/src/core/logic/Replace.ts @@ -0,0 +1,14 @@ +/** + * Replaces the property definitions from a given type + * @example + * ```typescript + * type Post { + * id: string; + * name: string; + * } + * + * Replace + * ``` + **/ + +export type Replace = Omit & R; diff --git a/src/domain/entities/.gitkeep b/src/domain/entities/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/domain/entities/user.entity.ts b/src/domain/entities/user.entity.ts new file mode 100644 index 0000000..3e707eb --- /dev/null +++ b/src/domain/entities/user.entity.ts @@ -0,0 +1,37 @@ +import { Entity } from '@core/domain/Entity'; +import { Replace } from '@core/logic/Replace'; + +export type UserProps = { + email: string; + createdAt: Date; +}; + +export class User extends Entity { + get email() { + return this.props.email; + } + + get createdAt() { + return this.props.createdAt; + } + + static create( + props: Replace< + UserProps, + { + createdAt?: Date; + } + >, + id?: string, + ) { + const user = new User( + { + ...props, + createdAt: props.createdAt ?? new Date(), + }, + id, + ); + + return user; + } +} diff --git a/src/domain/value-objects/email.ts b/src/domain/value-objects/email.ts new file mode 100644 index 0000000..6d7d358 --- /dev/null +++ b/src/domain/value-objects/email.ts @@ -0,0 +1,27 @@ +import { Either, left, right } from '@core/logic/Either'; + +import { EmailBadFormattedError } from './errors/email-bad-formatted-error'; + +export class Email { + protected constructor(private readonly email: string) {} + + get value(): string { + return this.email; + } + + static validate(email: string): boolean { + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + + return emailRegex.test(email); + } + + static create(email: string): Either { + const isValidEmail = this.validate(email); + + if (!isValidEmail) { + return left(new EmailBadFormattedError(email)); + } + + return right(new Email(email)); + } +} diff --git a/src/domain/value-objects/errors/email-bad-formatted-error.ts b/src/domain/value-objects/errors/email-bad-formatted-error.ts new file mode 100644 index 0000000..da99df0 --- /dev/null +++ b/src/domain/value-objects/errors/email-bad-formatted-error.ts @@ -0,0 +1,8 @@ +import { DomainError } from '@core/domain/errors/DomainError'; + +export class EmailBadFormattedError extends Error implements DomainError { + constructor(email: string) { + super(`The email '${email}' is bad formatted.`); + this.name = 'EmailBadFormatted'; + } +} diff --git a/src/infra/database/database.module.ts b/src/infra/database/database.module.ts index d754df5..86b7c02 100644 --- a/src/infra/database/database.module.ts +++ b/src/infra/database/database.module.ts @@ -1,9 +1,17 @@ import { Module } from '@nestjs/common'; import { PrismaService } from './prisma/prisma.service'; +import { PrismaUsersRepository } from './prisma/repositories/prisma-users-repository'; +import { UsersRepository } from './repositories/users.repository'; @Module({ - providers: [PrismaService], - exports: [PrismaService], + providers: [ + PrismaService, + { + provide: UsersRepository, + useClass: PrismaUsersRepository, + }, + ], + exports: [PrismaService, UsersRepository], }) export class DatabaseModule {} diff --git a/src/infra/database/prisma/mappers/.gitkeep b/src/infra/database/prisma/mappers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/infra/database/prisma/mappers/user.mapper.ts b/src/infra/database/prisma/mappers/user.mapper.ts new file mode 100644 index 0000000..57775d1 --- /dev/null +++ b/src/infra/database/prisma/mappers/user.mapper.ts @@ -0,0 +1,22 @@ +import { Prisma, User as RawUser } from '@prisma/client'; + +import { User } from '@domain/entities/user.entity'; + +export class UserMapper { + static toDomain(raw: RawUser): User { + const user = User.create({ + email: raw.email, + createdAt: raw.created_at, + }); + + return user; + } + + static toPersistence(user: User): Prisma.UserCreateInput { + return { + id: user.id, + email: user.email, + created_at: user.createdAt, + }; + } +} diff --git a/src/infra/database/prisma/repositories/.gitkeep b/src/infra/database/prisma/repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/infra/database/prisma/repositories/prisma-users-repository.ts b/src/infra/database/prisma/repositories/prisma-users-repository.ts new file mode 100644 index 0000000..fa37be0 --- /dev/null +++ b/src/infra/database/prisma/repositories/prisma-users-repository.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common'; + +import { AsyncMaybe } from '@core/logic/Maybe'; + +import { User } from '@domain/entities/user.entity'; + +import { UsersRepository } from '@infra/database/repositories/users.repository'; + +import { UserMapper } from '../mappers/user.mapper'; +import { PrismaService } from '../prisma.service'; + +@Injectable() +export class PrismaUsersRepository implements UsersRepository { + constructor(private readonly prisma: PrismaService) {} + + async create(user: User): Promise { + await this.prisma.user.create({ + data: UserMapper.toPersistence(user), + }); + + return user; + } + + async findByEmail(email: string): AsyncMaybe { + const user = await this.prisma.user.findUnique({ + where: { + email, + }, + }); + + if (!user) { + return null; + } + + return UserMapper.toDomain(user); + } +} diff --git a/src/infra/database/repositories/.gitkeep b/src/infra/database/repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/infra/database/repositories/users.repository.ts b/src/infra/database/repositories/users.repository.ts new file mode 100644 index 0000000..6a39019 --- /dev/null +++ b/src/infra/database/repositories/users.repository.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; + +import { AsyncMaybe } from '@core/logic/Maybe'; + +import { User } from '@domain/entities/user.entity'; + +@Injectable() +export abstract class UsersRepository { + abstract create(user: User): Promise; + abstract findByEmail(email: string): AsyncMaybe; +} diff --git a/src/infra/http/graphql/dto/models/.gitkeep b/src/infra/http/graphql/dto/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/infra/http/graphql/dto/models/user.model.ts b/src/infra/http/graphql/dto/models/user.model.ts new file mode 100644 index 0000000..2153466 --- /dev/null +++ b/src/infra/http/graphql/dto/models/user.model.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { Field, ID, ObjectType } from '@nestjs/graphql'; + +import { Paginated } from '../../common/dto/models/paginated'; + +@ObjectType() +export class User { + @Field(() => ID) + id: string; + + @Field(() => String) + email: string; + + @Field(() => Date) + createdAt: Date; +} + +@Injectable() +export class PaginatedUsers extends Paginated(User) {} diff --git a/src/infra/http/graphql/resolvers/example.resolver.e2e-spec.ts b/src/infra/http/graphql/resolvers/example.resolver.e2e-spec.ts deleted file mode 100644 index 600b6db..0000000 --- a/src/infra/http/graphql/resolvers/example.resolver.e2e-spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test } from '@nestjs/testing'; -import request from 'supertest'; - -import { DatabaseModule } from '@infra/database/database.module'; -import { HttpModule } from '@infra/http/http.module'; - -describe('Example Resolver (e2e)', () => { - let app: INestApplication; - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - imports: [DatabaseModule, HttpModule], - providers: [], - }).compile(); - - app = moduleRef.createNestApplication(); - - await app.init(); - }); - - it('(Query) helloWorld', async () => { - const response = await request(app.getHttpServer()) - .post('/graphql') - .send({ - query: ` - query { - helloWorld - } - `, - }) - .expect(200); - - expect(response.body.data.helloWorld).toBe('Hello World!'); - }); -}); diff --git a/src/infra/http/graphql/resolvers/example.resolver.ts b/src/infra/http/graphql/resolvers/example.resolver.ts deleted file mode 100644 index 2b41870..0000000 --- a/src/infra/http/graphql/resolvers/example.resolver.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Query, Resolver } from '@nestjs/graphql'; - -@Resolver(() => String) -export class ExampleResolver { - @Query((_returns) => String) - async helloWorld() { - return 'Hello World!'; - } -} diff --git a/src/infra/http/graphql/resolvers/user.resolver.e2e-spec.ts b/src/infra/http/graphql/resolvers/user.resolver.e2e-spec.ts new file mode 100644 index 0000000..14ea22c --- /dev/null +++ b/src/infra/http/graphql/resolvers/user.resolver.e2e-spec.ts @@ -0,0 +1,57 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import request from 'supertest'; + +import { DatabaseModule } from '@infra/database/database.module'; +import { HttpModule } from '@infra/http/http.module'; + +import { UserFactory } from '@test/factories/users.factory'; + +describe('UserResolver (e2e)', () => { + let app: INestApplication; + let userFactory: UserFactory; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [DatabaseModule, HttpModule], + providers: [UserFactory], + }).compile(); + + app = moduleRef.createNestApplication(); + + userFactory = moduleRef.get(UserFactory); + + await app.init(); + }); + + it('(Query) userByEmail', async () => { + const user = await userFactory.makeUser(); + + const response = await request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query UserByEmail ($email: String!){ + userByEmail(email: $email) { + id + email + createdAt + } + } + `, + variables: { + email: user.email, + }, + }) + .expect(200); + + const { + data: { userByEmail: output }, + } = response.body; + + expect(output).toMatchObject({ + id: expect.any(String), + email: user.email, + }); + }); +}); diff --git a/src/infra/http/graphql/resolvers/user.resolver.ts b/src/infra/http/graphql/resolvers/user.resolver.ts new file mode 100644 index 0000000..5298a53 --- /dev/null +++ b/src/infra/http/graphql/resolvers/user.resolver.ts @@ -0,0 +1,23 @@ +import { Args, Query, Resolver } from '@nestjs/graphql'; + +import { GetUserByEmailUseCase } from '@application/use-cases/user/get-user-by-email.use-case'; + +import { User } from '../dto/models/user.model'; +import { UseCaseErrorViewModel } from '../view-models/use-case-error.view-model'; +import { UserViewModel } from '../view-models/user.view-model'; + +@Resolver(() => User) +export class UserResolver { + constructor(private readonly getUserByEmailUseCase: GetUserByEmailUseCase) {} + + @Query((_returns) => User) + async userByEmail(@Args('email') email: string) { + const output = await this.getUserByEmailUseCase.handle(email); + + if (output.isLeft()) { + return UseCaseErrorViewModel.toGraphQL(output.value); + } + + return UserViewModel.toGraphQL(output.value); + } +} diff --git a/src/infra/http/graphql/view-models/user.view-model.ts b/src/infra/http/graphql/view-models/user.view-model.ts new file mode 100644 index 0000000..75ad514 --- /dev/null +++ b/src/infra/http/graphql/view-models/user.view-model.ts @@ -0,0 +1,13 @@ +import { User as UserEntity } from '@domain/entities/user.entity'; + +import { User } from '../dto/models/user.model'; + +export class UserViewModel { + static toGraphQL(user: UserEntity): User { + return { + id: user.id, + email: user.email, + createdAt: user.createdAt, + }; + } +} diff --git a/src/infra/http/http.module.ts b/src/infra/http/http.module.ts index aed77c3..edd7434 100644 --- a/src/infra/http/http.module.ts +++ b/src/infra/http/http.module.ts @@ -3,17 +3,19 @@ import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { GraphQLModule } from '@nestjs/graphql'; -// import { DatabaseModule } from '@infra/database/database.module'; +import { UseCasesModule } from '@application/use-cases/user-case.module'; + +import { DatabaseModule } from '@infra/database/database.module'; import { ComplexityPlugin } from '@infra/http/graphql/complexity-plugin'; -import { ExampleResolver } from '@infra/http/graphql/resolvers/example.resolver'; import { JWTAuthGuard } from './auth/jwt-auth-guard'; import { JwtStrategy } from './auth/jwt.strategy'; +import { UserResolver } from './graphql/resolvers/user.resolver'; @Module({ imports: [ - // DatabaseModule, - + DatabaseModule, + UseCasesModule, GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: true, @@ -28,7 +30,7 @@ import { JwtStrategy } from './auth/jwt.strategy'; }), ], providers: [ - ExampleResolver, + UserResolver, JwtStrategy, { provide: APP_GUARD, diff --git a/test/factories/.gitkeep b/test/factories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/factories/users.factory.ts b/test/factories/users.factory.ts new file mode 100644 index 0000000..5057b57 --- /dev/null +++ b/test/factories/users.factory.ts @@ -0,0 +1,48 @@ +import faker from '@faker-js/faker'; +import { Injectable } from '@nestjs/common'; + +import { Replace } from '@core/logic/Replace'; + +import { User, UserProps } from '@domain/entities/user.entity'; + +import { UserMapper } from '@infra/database/prisma/mappers/user.mapper'; +import { PrismaService } from '@infra/database/prisma/prisma.service'; + +type Overrides = Partial< + Replace< + UserProps, + { + email?: string; + createdAt?: Date; + } + > +>; + +export function makeFakeUser(data = {} as Overrides) { + const email = faker.internet.email(); + const createdAt = faker.date.past(); + + const props: UserProps = { + email: data.email || email, + createdAt: data.createdAt || createdAt, + }; + + const user = User.create(props); + + return user; +} + +@Injectable() +export class UserFactory { + constructor(private prisma: PrismaService) {} + + async makeUser(data = {} as Overrides): Promise { + const user = makeFakeUser(data); + + await this.prisma.user.create({ + data: UserMapper.toPersistence(user), + }); + + return user; + } +} diff --git a/test/repositories/.gitkeep b/test/repositories/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/repositories/in-memory-users.repository.ts b/test/repositories/in-memory-users.repository.ts new file mode 100644 index 0000000..5a792f1 --- /dev/null +++ b/test/repositories/in-memory-users.repository.ts @@ -0,0 +1,25 @@ +import { AsyncMaybe } from '@core/logic/Maybe'; + +import { User } from '@domain/entities/user.entity'; + +import { UsersRepository } from '@infra/database/repositories/users.repository'; + +export class InMemoryUsersRepository implements UsersRepository { + public items: Array = []; + + async create(user: User): Promise { + this.items.push(user); + + return user; + } + + async findByEmail(email: string): AsyncMaybe { + const user = this.items.find((item) => item.email === email); + + if (!user) { + return null; + } + + return user; + } +}