From a1bdfdb4c8447126ab85503bb7db4ce4afed9b35 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 17:36:56 +0900 Subject: [PATCH 01/15] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20use-case?= =?UTF-8?q?=20to=20use-cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/users/application/src/index.ts | 2 +- .../src/lib/{use-case => use-cases}/get-user.use-case.spec.ts | 0 .../src/lib/{use-case => use-cases}/get-user.use-case.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename libs/users/application/src/lib/{use-case => use-cases}/get-user.use-case.spec.ts (100%) rename libs/users/application/src/lib/{use-case => use-cases}/get-user.use-case.ts (100%) diff --git a/libs/users/application/src/index.ts b/libs/users/application/src/index.ts index c5a7c7d..192fddd 100644 --- a/libs/users/application/src/index.ts +++ b/libs/users/application/src/index.ts @@ -1,4 +1,4 @@ -export * from './lib/use-case/get-user.use-case'; +export * from './lib/use-cases/get-user.use-case'; // service export * from './lib/users.service'; diff --git a/libs/users/application/src/lib/use-case/get-user.use-case.spec.ts b/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts similarity index 100% rename from libs/users/application/src/lib/use-case/get-user.use-case.spec.ts rename to libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts diff --git a/libs/users/application/src/lib/use-case/get-user.use-case.ts b/libs/users/application/src/lib/use-cases/get-user.use-case.ts similarity index 100% rename from libs/users/application/src/lib/use-case/get-user.use-case.ts rename to libs/users/application/src/lib/use-cases/get-user.use-case.ts From 66310ace6425babe7052e6686b4efa64826da026 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 17:39:10 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=E2=9C=A8=20getUserUseCase=20in?= =?UTF-8?q?=20UsersService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/src/lib/users.service.spec.ts | 18 ++++++--- .../application/src/lib/users.service.ts | 11 +++--- .../src/lib/resolver/users.resolver.spec.ts | 39 +++++-------------- .../src/lib/resolver/users.resolver.ts | 13 +------ 4 files changed, 28 insertions(+), 53 deletions(-) diff --git a/libs/users/application/src/lib/users.service.spec.ts b/libs/users/application/src/lib/users.service.spec.ts index 85a8661..2065472 100644 --- a/libs/users/application/src/lib/users.service.spec.ts +++ b/libs/users/application/src/lib/users.service.spec.ts @@ -18,13 +18,19 @@ describe('UsersService', () => { expect(service).toBeDefined(); }); - it('should return a user by id', () => { - const user: User | undefined = service.findById('1'); - expect(user).toEqual({ id: '1', name: 'John Doe' }); + it('should return a user by id', async () => { + const user = new User('1', 'John Doe'); + jest.spyOn(service, 'findById').mockResolvedValue(user); + + const result = await service.findById('1'); + expect(result).toEqual(user); }); - it('should return undefined if user is not found', () => { - const user: User | undefined = service.findById('3'); - expect(user).toBeUndefined(); + it('should return undefined if user is not found', async () => { + jest.spyOn(service, 'findById').mockResolvedValue(null); + + const result = await service.findById('1'); + expect(result).toBeNull(); + }); }); diff --git a/libs/users/application/src/lib/users.service.ts b/libs/users/application/src/lib/users.service.ts index b605946..8583406 100644 --- a/libs/users/application/src/lib/users.service.ts +++ b/libs/users/application/src/lib/users.service.ts @@ -1,14 +1,13 @@ import { Injectable } from '@nestjs/common'; import { User } from '@users/domain'; +import { GetUserUseCase } from './use-cases/get-user.use-case'; + @Injectable() export class UsersService { - private users: User[] = [ - { id: '1', name: 'John Doe' }, - { id: '2', name: 'Richard Roe' }, - ]; + constructor(private readonly getUserUseCase: GetUserUseCase) {} - findById(id: string): User | undefined { - return this.users.find((user) => user.id === id); + async findById(id: string): Promise { + return this.getUserUseCase.execute(id); } } diff --git a/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts b/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts index a7bb40e..0d3d062 100644 --- a/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts +++ b/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts @@ -30,40 +30,19 @@ describe('UsersResolver', () => { }); describe('getUser', () => { - it('should return a user by id', () => { - const user: User = { id: '1', name: 'John Doe' }; - jest.spyOn(service, 'findById').mockReturnValue(user); + it('should return a user by id', async () => { + const user = new User('1', 'John Doe'); + jest.spyOn(service, 'findById').mockResolvedValue(user); - expect(resolver.getUser('1')).toEqual(user); - expect(service.findById).toHaveBeenCalledWith('1'); + const result = await resolver.getUser('1'); + expect(result).toEqual(user); }); - it('should return undefined if user not found', () => { - jest.spyOn(service, 'findById').mockReturnValue(undefined); + it('should return undefined if user not found', async () => { + jest.spyOn(service, 'findById').mockResolvedValue(null); - expect(resolver.getUser('2')).toBeNull(); - expect(service.findById).toHaveBeenCalledWith('2'); - }); - }); - - describe('resolveReference', () => { - it('should return a user by reference id', () => { - const user: User = { id: '1', name: 'John Doe' }; - jest.spyOn(service, 'findById').mockReturnValue(user); - - expect( - resolver.resolveReference({ __typename: 'User', id: '1' }), - ).toEqual(user); - expect(service.findById).toHaveBeenCalledWith('1'); - }); - - it('should return undefined if user not found by reference id', () => { - jest.spyOn(service, 'findById').mockReturnValue(undefined); - - expect( - resolver.resolveReference({ __typename: 'User', id: '2' }), - ).toBeUndefined(); - expect(service.findById).toHaveBeenCalledWith('2'); + const result = await resolver.getUser('1'); + expect(result).toBeNull(); }); }); }); diff --git a/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts b/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts index ca63644..30736be 100644 --- a/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts +++ b/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts @@ -9,16 +9,7 @@ export class UsersResolver { constructor(private usersService: UsersService) {} @Query(() => UserDto, { nullable: true }) - getUser(@Args({ name: 'id', type: () => ID }) id: string): UserDto | null { - const user: User | undefined = this.usersService.findById(id); // Domain entity - return user ? new UserDto(user.id, user.name) : null; - } - - @ResolveReference() - resolveReference(reference: { - __typename: string; - id: string; - }): User | undefined { - return this.usersService.findById(reference.id); + getUser(@Args({ name: 'id', type: () => ID }) id: string): Promise { + return this.usersService.findById(id); } } From bb583de18104d259ae2b9bff5e0fe9d9fb2189ab Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 17:58:39 +0900 Subject: [PATCH 03/15] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Inject=20GetUserUse?= =?UTF-8?q?Case=20in=20UsersModule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/src/users/users.module.ts | 4 ++-- .../interface-adapters/src/lib/resolver/users.resolver.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/users/src/users/users.module.ts b/apps/users/src/users/users.module.ts index 1ec9dce..9f406ce 100644 --- a/apps/users/src/users/users.module.ts +++ b/apps/users/src/users/users.module.ts @@ -5,12 +5,12 @@ import { } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; -import { UsersService } from '@users/application'; +import { UsersService, GetUserUseCase } from '@users/application'; import { UsersResolver } from '@users/interface-adapters'; import { DatabaseModule } from '@shared/infrastructure-mongoose'; @Module({ - providers: [UsersResolver, UsersService], + providers: [UsersResolver, UsersService, GetUserUseCase], imports: [ GraphQLModule.forRoot({ driver: ApolloFederationDriver, diff --git a/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts b/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts index 30736be..2d28b8b 100644 --- a/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts +++ b/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts @@ -1,6 +1,5 @@ -import { Args, ID, Query, Resolver, ResolveReference } from '@nestjs/graphql'; +import { Args, ID, Query, Resolver } from '@nestjs/graphql'; import { UsersService } from '@users/application'; -import { User } from '@users/domain'; import { UserDto } from '../dto/user.dto'; From 191b4ddcf30b4024ed2782c8e484f4db9d338fa2 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 18:05:09 +0900 Subject: [PATCH 04/15] =?UTF-8?q?test:=20=F0=9F=A7=AA=20inject=20GetUserUs?= =?UTF-8?q?eCase=20in=20test=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/users/application/src/lib/users.service.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/users/application/src/lib/users.service.spec.ts b/libs/users/application/src/lib/users.service.spec.ts index 2065472..55f3842 100644 --- a/libs/users/application/src/lib/users.service.spec.ts +++ b/libs/users/application/src/lib/users.service.spec.ts @@ -2,13 +2,14 @@ import { Test, TestingModule } from '@nestjs/testing'; import { User } from '@users/domain'; import { UsersService } from './users.service'; +import { GetUserUseCase } from './use-cases/get-user.use-case'; describe('UsersService', () => { let service: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UsersService], + providers: [UsersService, GetUserUseCase], }).compile(); service = module.get(UsersService); From 1ee97da6d53cf7df8262cd880b049b06381245e9 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 21:12:09 +0900 Subject: [PATCH 05/15] =?UTF-8?q?docs:=20=F0=9F=93=9D=20MongooseUserReposi?= =?UTF-8?q?tory=20to=20MongooseUsersRepository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f70b6a..ab68b9d 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | resolver(interface-adapters) | Define GraphQL schema and resolver. | | dto(interface-adapters) | Define DTOs for GraphQL schema. | -| mongoose(infrastructure) | Implements the repository interfaces defined in the domain layer using Mongoose as the ODM (Object Document Mapper).
Includes Mongoose Schema definitions, database connection management, and concrete implementations of repository interfaces (e.g., MongooseUserRepository). | +| mongoose(infrastructure) | Implements the repository interfaces defined in the domain layer using Mongoose as the ODM (Object Document Mapper).
Includes Mongoose Schema definitions, database connection management, and concrete implementations of repository interfaces (e.g., MongooseUsersRepository). | | service(application) | As the core of the application layer, it mainly interacts with the domain layer and interface-adapter layer.
If you migrate to a non-NestJS architecture in the future (e.g. other frameworks or microservices), the application tier code can be left unaffected. | | use-case(application) | Define business use cases and encapsulate business logic. | | entity(domain) | Define core business entities and business rules.
Maintain entity independence from database and framework. | From 4845fa449b4f3d7916f9042b008d40a929edf85e Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 21:12:42 +0900 Subject: [PATCH 06/15] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20UserRepo?= =?UTF-8?q?sitory=20to=20UsersRepository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...repository.spec.ts => users.repository.spec.ts} | 14 +++++++------- .../{user.repository.ts => users.repository.ts} | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) rename libs/users/domain/src/lib/{user.repository.spec.ts => users.repository.spec.ts} (60%) rename libs/users/domain/src/lib/{user.repository.ts => users.repository.ts} (71%) diff --git a/libs/users/domain/src/lib/user.repository.spec.ts b/libs/users/domain/src/lib/users.repository.spec.ts similarity index 60% rename from libs/users/domain/src/lib/user.repository.spec.ts rename to libs/users/domain/src/lib/users.repository.spec.ts index dbac875..93f4ec3 100644 --- a/libs/users/domain/src/lib/user.repository.spec.ts +++ b/libs/users/domain/src/lib/users.repository.spec.ts @@ -1,7 +1,7 @@ -import { UserRepository } from './user.repository'; +import { UsersRepository } from './users.repository'; import { User } from './user.entity'; -class MockUserRepository implements UserRepository { +class MockUsersRepository implements UsersRepository { private users: User[] = [ { id: '1', name: 'John Doe' }, { id: '2', name: 'Jane Doe' }, @@ -12,20 +12,20 @@ class MockUserRepository implements UserRepository { } } -describe('UserRepository', () => { - let userRepository: UserRepository; +describe('UsersRepository', () => { + let usersRepository: UsersRepository; beforeEach(() => { - userRepository = new MockUserRepository(); + usersRepository = new MockUsersRepository(); }); test('findById should return a user by id', async () => { - const user = await userRepository.findById('1'); + const user = await usersRepository.findById('1'); expect(user).toEqual({ id: '1', name: 'John Doe' }); }); test('findById should return null if user not found', async () => { - const user = await userRepository.findById('3'); + const user = await usersRepository.findById('3'); expect(user).toBeNull(); }); }); diff --git a/libs/users/domain/src/lib/user.repository.ts b/libs/users/domain/src/lib/users.repository.ts similarity index 71% rename from libs/users/domain/src/lib/user.repository.ts rename to libs/users/domain/src/lib/users.repository.ts index a3e9686..e80905f 100644 --- a/libs/users/domain/src/lib/user.repository.ts +++ b/libs/users/domain/src/lib/users.repository.ts @@ -1,5 +1,5 @@ import { User } from './user.entity'; -export interface UserRepository { +export interface UsersRepository { findById(id: string): Promise; } From b37adaa726dcb5d87e15781eb2bd2bf406b511b9 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 21:13:08 +0900 Subject: [PATCH 07/15] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20user.rep?= =?UTF-8?q?ository=20to=20users.repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/users/domain/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/users/domain/src/index.ts b/libs/users/domain/src/index.ts index 9602d75..7e85695 100644 --- a/libs/users/domain/src/index.ts +++ b/libs/users/domain/src/index.ts @@ -1,3 +1,2 @@ -// user export * from './lib/user.entity'; -export * from './lib/user.repository'; +export * from './lib/users.repository'; From c725fcedf1510fc2bdce654f873171332a690ecc Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 21:15:43 +0900 Subject: [PATCH 08/15] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20Mongoose?= =?UTF-8?q?UserRepository=20to=20MongooseUsersRepository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ory.spec.ts => mongoose-users.repository.spec.ts} | 10 +++++----- ...er.repository.ts => mongoose-users.repository.ts} | 12 +++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) rename libs/users/infrastructure/mongoose/src/lib/{mongoose-user.repository.spec.ts => mongoose-users.repository.spec.ts} (82%) rename libs/users/infrastructure/mongoose/src/lib/{mongoose-user.repository.ts => mongoose-users.repository.ts} (56%) diff --git a/libs/users/infrastructure/mongoose/src/lib/mongoose-user.repository.spec.ts b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts similarity index 82% rename from libs/users/infrastructure/mongoose/src/lib/mongoose-user.repository.spec.ts rename to libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts index 412edd7..a31ff77 100644 --- a/libs/users/infrastructure/mongoose/src/lib/mongoose-user.repository.spec.ts +++ b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts @@ -1,18 +1,18 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getModelToken } from '@nestjs/mongoose'; import { Model } from 'mongoose'; -import { MongooseUserRepository } from './mongoose-user.repository'; +import { MongooseUsersRepository } from './mongoose-users.repository'; import { UserDocument } from './user.schema'; import { User } from '@users/domain'; -describe('MongooseUserRepository', () => { - let repository: MongooseUserRepository; +describe('MongooseUsersRepository', () => { + let repository: MongooseUsersRepository; let userModel: Model; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - MongooseUserRepository, + MongooseUsersRepository, { provide: getModelToken(UserDocument.name), useValue: { @@ -22,7 +22,7 @@ describe('MongooseUserRepository', () => { ], }).compile(); - repository = module.get(MongooseUserRepository); + repository = module.get(MongooseUsersRepository); userModel = module.get>(getModelToken(UserDocument.name)); }); diff --git a/libs/users/infrastructure/mongoose/src/lib/mongoose-user.repository.ts b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.ts similarity index 56% rename from libs/users/infrastructure/mongoose/src/lib/mongoose-user.repository.ts rename to libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.ts index 70042a0..ed004d4 100644 --- a/libs/users/infrastructure/mongoose/src/lib/mongoose-user.repository.ts +++ b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.ts @@ -1,16 +1,22 @@ +import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; -import { User, UserRepository } from '@users/domain'; +import { User, UsersRepository } from '@users/domain'; import { Model } from 'mongoose'; import { UserDocument } from './user.schema'; -export class MongooseUserRepository implements UserRepository { +@Injectable() +export class MongooseUsersRepository implements UsersRepository { constructor( @InjectModel(UserDocument.name) private userModel: Model, ) {} async findById(id: string): Promise { const userDoc = await this.userModel.findById(id).exec(); - return userDoc ? new User(userDoc.id, userDoc.name) : null; + + if (!userDoc) { + return null; + } + return new User(userDoc.id, userDoc.name); } } From 41458b8cb6dad4cb6e2fe48d07c36d0772d3672f Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 21:16:07 +0900 Subject: [PATCH 09/15] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20mongoose?= =?UTF-8?q?-user=20to=20mongoose-users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/users/infrastructure/mongoose/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/users/infrastructure/mongoose/src/index.ts b/libs/users/infrastructure/mongoose/src/index.ts index ac4244f..9310eb9 100644 --- a/libs/users/infrastructure/mongoose/src/index.ts +++ b/libs/users/infrastructure/mongoose/src/index.ts @@ -1,2 +1,2 @@ -export * from './lib/mongoose-user.repository'; +export * from './lib/mongoose-users.repository'; export * from './lib/user.schema'; \ No newline at end of file From 3886d0d01a6d0d6e3369bfc97772eb4bcc4addaa Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Tue, 19 Nov 2024 21:19:55 +0900 Subject: [PATCH 10/15] =?UTF-8?q?test:=20=F0=9F=A7=AA=20return=20undefined?= =?UTF-8?q?=20to=20return=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/users/application/src/lib/users.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/users/application/src/lib/users.service.spec.ts b/libs/users/application/src/lib/users.service.spec.ts index 55f3842..5b53dd5 100644 --- a/libs/users/application/src/lib/users.service.spec.ts +++ b/libs/users/application/src/lib/users.service.spec.ts @@ -27,7 +27,7 @@ describe('UsersService', () => { expect(result).toEqual(user); }); - it('should return undefined if user is not found', async () => { + it('should return null if user is not found', async () => { jest.spyOn(service, 'findById').mockResolvedValue(null); const result = await service.findById('1'); From fae29b58feeac38075b9fdb3b813661c4146a9df Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Wed, 20 Nov 2024 12:06:50 +0900 Subject: [PATCH 11/15] =?UTF-8?q?feat:=20=E2=9C=A8=20make=20GetUserUseCase?= =?UTF-8?q?=20be=20injectbale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/use-cases/get-user.use-case.spec.ts | 18 +++++++++--------- .../src/lib/use-cases/get-user.use-case.ts | 16 +++++++++++++--- libs/users/domain/src/lib/users.repository.ts | 2 ++ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts b/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts index 650e7d2..47dbcb9 100644 --- a/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts +++ b/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts @@ -1,36 +1,36 @@ import { GetUserUseCase } from './get-user.use-case'; -import { UserRepository } from '@users/domain'; +import { UsersRepository } from '@users/domain'; describe('GetUserUseCase', () => { let getUserUseCase: GetUserUseCase; - let userRepository: jest.Mocked; + let usersRepository: jest.Mocked; beforeEach(() => { - userRepository = { + usersRepository = { findById: jest.fn(), - } as unknown as jest.Mocked; + } as unknown as jest.Mocked; - getUserUseCase = new GetUserUseCase(userRepository); + getUserUseCase = new GetUserUseCase(usersRepository); }); describe('execute', () => { it('should return a user when found', async () => { const user = { id: '1', name: 'John Doe' }; - userRepository.findById.mockResolvedValue(user); + usersRepository.findById.mockResolvedValue(user); const result = await getUserUseCase.execute('1'); expect(result).toEqual(user); - expect(userRepository.findById).toHaveBeenCalledWith('1'); + expect(usersRepository.findById).toHaveBeenCalledWith('1'); }); it('should return null when user is not found', async () => { - userRepository.findById.mockResolvedValue(null); + usersRepository.findById.mockResolvedValue(null); const result = await getUserUseCase.execute('1'); expect(result).toBeNull(); - expect(userRepository.findById).toHaveBeenCalledWith('1'); + expect(usersRepository.findById).toHaveBeenCalledWith('1'); }); }); }); \ No newline at end of file diff --git a/libs/users/application/src/lib/use-cases/get-user.use-case.ts b/libs/users/application/src/lib/use-cases/get-user.use-case.ts index a922928..25e328f 100644 --- a/libs/users/application/src/lib/use-cases/get-user.use-case.ts +++ b/libs/users/application/src/lib/use-cases/get-user.use-case.ts @@ -1,9 +1,19 @@ -import { User, UserRepository } from '@users/domain'; +import { Inject, Injectable } from '@nestjs/common'; +import { User, USERS_REPOSITORY, UsersRepository } from '@users/domain'; +@Injectable() export class GetUserUseCase { - constructor(private readonly userRepository: UserRepository) {} + constructor( + @Inject(USERS_REPOSITORY) + private readonly usersRepository: UsersRepository, + ) {} async execute(id: string): Promise { - return this.userRepository.findById(id); + const user = this.usersRepository.findById(id); + + if (!user) { + return null; + } + return user; } } diff --git a/libs/users/domain/src/lib/users.repository.ts b/libs/users/domain/src/lib/users.repository.ts index e80905f..4af6be0 100644 --- a/libs/users/domain/src/lib/users.repository.ts +++ b/libs/users/domain/src/lib/users.repository.ts @@ -3,3 +3,5 @@ import { User } from './user.entity'; export interface UsersRepository { findById(id: string): Promise; } + +export const USERS_REPOSITORY = Symbol('USERS_REPOSITORY'); \ No newline at end of file From b386cdb7a08a48d1e56d15c6a63014ae0839881c Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Wed, 20 Nov 2024 12:08:06 +0900 Subject: [PATCH 12/15] =?UTF-8?q?feat:=20=E2=9C=A8=20import=20MongooseModu?= =?UTF-8?q?le=20and=20provide=20USERS=5FREPOSITORY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/src/users/users.module.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/users/src/users/users.module.ts b/apps/users/src/users/users.module.ts index 9f406ce..f0ed7b7 100644 --- a/apps/users/src/users/users.module.ts +++ b/apps/users/src/users/users.module.ts @@ -5,12 +5,27 @@ import { } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; +import { DatabaseModule } from '@shared/infrastructure-mongoose'; import { UsersService, GetUserUseCase } from '@users/application'; import { UsersResolver } from '@users/interface-adapters'; -import { DatabaseModule } from '@shared/infrastructure-mongoose'; +import { + MongooseUsersRepository, + UserDocument, + UserSchema, +} from '@users/infrastructure-mongoose'; +import { MongooseModule } from '@nestjs/mongoose'; +import { USERS_REPOSITORY } from '@users/domain'; @Module({ - providers: [UsersResolver, UsersService, GetUserUseCase], + providers: [ + UsersResolver, + UsersService, + GetUserUseCase, + { + provide: USERS_REPOSITORY, + useClass: MongooseUsersRepository, + }, + ], imports: [ GraphQLModule.forRoot({ driver: ApolloFederationDriver, @@ -27,6 +42,7 @@ import { DatabaseModule } from '@shared/infrastructure-mongoose'; plugins: [ApolloServerPluginInlineTrace()], }), DatabaseModule, + MongooseModule.forFeature([{ name: UserDocument.name, schema: UserSchema }]) ], }) export class UsersModule {} From df0636c0b5c6e28271ef8973abf30f5fa394a9de Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Wed, 20 Nov 2024 12:08:39 +0900 Subject: [PATCH 13/15] =?UTF-8?q?test:=20=F0=9F=A7=AA=20test=20case=20unde?= =?UTF-8?q?fined=20to=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface-adapters/src/lib/resolver/users.resolver.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts b/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts index 0d3d062..56b63ed 100644 --- a/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts +++ b/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts @@ -38,7 +38,7 @@ describe('UsersResolver', () => { expect(result).toEqual(user); }); - it('should return undefined if user not found', async () => { + it('should return null if user not found', async () => { jest.spyOn(service, 'findById').mockResolvedValue(null); const result = await resolver.getUser('1'); From b09be75e3ee481782b9273be6bcae9045b7fa95f Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Wed, 20 Nov 2024 12:37:37 +0900 Subject: [PATCH 14/15] =?UTF-8?q?test:=20=F0=9F=A7=AA=20test=20cases=20of?= =?UTF-8?q?=20UsersService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/use-cases/get-user.use-case.spec.ts | 17 ++++----- .../application/src/lib/users.service.spec.ts | 38 ++++++++++++------- .../src/lib/resolver/users.resolver.spec.ts | 17 --------- 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts b/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts index 47dbcb9..7debc7a 100644 --- a/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts +++ b/libs/users/application/src/lib/use-cases/get-user.use-case.spec.ts @@ -3,29 +3,28 @@ import { UsersRepository } from '@users/domain'; describe('GetUserUseCase', () => { let getUserUseCase: GetUserUseCase; - let usersRepository: jest.Mocked; + let usersRepository: UsersRepository; beforeEach(() => { usersRepository = { findById: jest.fn(), - } as unknown as jest.Mocked; - + }; getUserUseCase = new GetUserUseCase(usersRepository); }); describe('execute', () => { - it('should return a user when found', async () => { - const user = { id: '1', name: 'John Doe' }; - usersRepository.findById.mockResolvedValue(user); + it('should return user when found', async () => { + const mockUser = { id: '1', name: 'John Doe' }; + (usersRepository.findById as jest.Mock).mockResolvedValue(mockUser); const result = await getUserUseCase.execute('1'); - expect(result).toEqual(user); + expect(result).toEqual(mockUser); expect(usersRepository.findById).toHaveBeenCalledWith('1'); }); - it('should return null when user is not found', async () => { - usersRepository.findById.mockResolvedValue(null); + it('should return null when user not found', async () => { + (usersRepository.findById as jest.Mock).mockResolvedValue(null); const result = await getUserUseCase.execute('1'); diff --git a/libs/users/application/src/lib/users.service.spec.ts b/libs/users/application/src/lib/users.service.spec.ts index 5b53dd5..c3879e0 100644 --- a/libs/users/application/src/lib/users.service.spec.ts +++ b/libs/users/application/src/lib/users.service.spec.ts @@ -1,37 +1,47 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { User } from '@users/domain'; - import { UsersService } from './users.service'; import { GetUserUseCase } from './use-cases/get-user.use-case'; +import { User } from '@users/domain'; describe('UsersService', () => { let service: UsersService; + let getUserUseCase: GetUserUseCase; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UsersService, GetUserUseCase], + providers: [ + UsersService, + { + provide: GetUserUseCase, + useValue: { + execute: jest.fn(), + }, + }, + ], }).compile(); service = module.get(UsersService); + getUserUseCase = module.get(GetUserUseCase); }); it('should be defined', () => { expect(service).toBeDefined(); }); - it('should return a user by id', async () => { - const user = new User('1', 'John Doe'); - jest.spyOn(service, 'findById').mockResolvedValue(user); + describe('findById', () => { + it('should return a user if found', async () => { + const user: User = { id: '1', name: 'John Doe' }; + jest.spyOn(getUserUseCase, 'execute').mockResolvedValue(user); - const result = await service.findById('1'); - expect(result).toEqual(user); - }); + const result = await service.findById('1'); + expect(result).toEqual(user); + }); - it('should return null if user is not found', async () => { - jest.spyOn(service, 'findById').mockResolvedValue(null); + it('should return null if user not found', async () => { + jest.spyOn(getUserUseCase, 'execute').mockResolvedValue(null); - const result = await service.findById('1'); - expect(result).toBeNull(); - + const result = await service.findById('2'); + expect(result).toBeNull(); + }); }); }); diff --git a/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts b/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts index 56b63ed..1bbf9ec 100644 --- a/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts +++ b/libs/users/interface-adapters/src/lib/resolver/users.resolver.spec.ts @@ -28,21 +28,4 @@ describe('UsersResolver', () => { it('should be defined', () => { expect(resolver).toBeDefined(); }); - - describe('getUser', () => { - it('should return a user by id', async () => { - const user = new User('1', 'John Doe'); - jest.spyOn(service, 'findById').mockResolvedValue(user); - - const result = await resolver.getUser('1'); - expect(result).toEqual(user); - }); - - it('should return null if user not found', async () => { - jest.spyOn(service, 'findById').mockResolvedValue(null); - - const result = await resolver.getUser('1'); - expect(result).toBeNull(); - }); - }); }); From 84db5681af6a7f89c954a3ebc28788b53aa5fff5 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Wed, 20 Nov 2024 12:38:20 +0900 Subject: [PATCH 15/15] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Missing=20await=20f?= =?UTF-8?q?or=20async=20repository=20call?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/users/application/src/lib/use-cases/get-user.use-case.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/users/application/src/lib/use-cases/get-user.use-case.ts b/libs/users/application/src/lib/use-cases/get-user.use-case.ts index 25e328f..be0f1e8 100644 --- a/libs/users/application/src/lib/use-cases/get-user.use-case.ts +++ b/libs/users/application/src/lib/use-cases/get-user.use-case.ts @@ -9,7 +9,7 @@ export class GetUserUseCase { ) {} async execute(id: string): Promise { - const user = this.usersRepository.findById(id); + const user = await this.usersRepository.findById(id); if (!user) { return null;