From 6c6ced07d2e50f37a510c5089f1ac50ff63db399 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 10:52:55 +0900 Subject: [PATCH 01/22] =?UTF-8?q?docs:=20=F0=9F=93=9D=20remove=20enter=20s?= =?UTF-8?q?pace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 8ad1d41..5390021 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx | resolver(interface-adapters) | Handles GraphQL queries and mutations by converting them into calls to the application layer.
Responsible for input validation and response formatting specific to GraphQL. | | dto(interface-adapters) | Define DTOs for GraphQL schema. | | infrastructure | Implements the technical capabilities needed to support the higher layers of the application.
Handles database connections, external service integrations, and other technical concerns.
Contains concrete implementations of repository interfaces defined in the domain layer. | - | 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).
Adding validation in the Mongoose schema ensures that any data persisted to the database adheres to the required constraints. This helps maintain data integrity and prevents invalid or duplicate entries at the database level. | | 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.
Implementing validation in the use-case layer allows you to enforce business logic and provide immediate feedback to users or calling services. This is where you can handle complex validation rules and provide detailed error messages.| From efb88cdc67b951b9198dc3e8af8f8f9aba912bb5 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 14:00:23 +0900 Subject: [PATCH 02/22] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20tasks=20domain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/domain/src/index.ts | 5 +- .../src/lib/entities/task.entity.spec.ts | 39 ++++++++++ .../domain/src/lib/entities/task.entity.ts | 8 ++ .../src/lib/entities/user-task.entity.spec.ts | 30 ++++++++ .../src/lib/entities/user-task.entity.ts | 15 ++++ .../domain/src/lib/task.repository.spec.ts | 76 +++++++++++++++++++ libs/tasks/domain/src/lib/task.repository.ts | 9 +++ .../tasks/domain/src/lib/tasks-domain.spec.ts | 7 -- libs/tasks/domain/src/lib/tasks-domain.ts | 3 - 9 files changed, 181 insertions(+), 11 deletions(-) create mode 100644 libs/tasks/domain/src/lib/entities/task.entity.spec.ts create mode 100644 libs/tasks/domain/src/lib/entities/task.entity.ts create mode 100644 libs/tasks/domain/src/lib/entities/user-task.entity.spec.ts create mode 100644 libs/tasks/domain/src/lib/entities/user-task.entity.ts create mode 100644 libs/tasks/domain/src/lib/task.repository.spec.ts create mode 100644 libs/tasks/domain/src/lib/task.repository.ts delete mode 100644 libs/tasks/domain/src/lib/tasks-domain.spec.ts delete mode 100644 libs/tasks/domain/src/lib/tasks-domain.ts diff --git a/libs/tasks/domain/src/index.ts b/libs/tasks/domain/src/index.ts index 810dfe0..d134930 100644 --- a/libs/tasks/domain/src/index.ts +++ b/libs/tasks/domain/src/index.ts @@ -1 +1,4 @@ -export * from './lib/tasks-domain'; +export * from './lib/entities/task.entity'; +export * from './lib/entities/user-task.entity'; + +export * from './lib/task.repository'; diff --git a/libs/tasks/domain/src/lib/entities/task.entity.spec.ts b/libs/tasks/domain/src/lib/entities/task.entity.spec.ts new file mode 100644 index 0000000..f86601b --- /dev/null +++ b/libs/tasks/domain/src/lib/entities/task.entity.spec.ts @@ -0,0 +1,39 @@ +import { Task } from './task.entity'; + +describe('Task', () => { + it('should create a task with all properties', () => { + const task = new Task( + 'test-id', + 'Test Title', + 'Test Description', + ['category1', 'category2'] + ); + + expect(task.id).toBe('test-id'); + expect(task.title).toBe('Test Title'); + expect(task.description).toBe('Test Description'); + expect(task.categories).toEqual(['category1', 'category2']); + }); + + it('should allow null description', () => { + const task = new Task( + 'test-id', + 'Test Title', + null, + ['category1'] + ); + + expect(task.description).toBeNull(); + }); + + it('should create a task with empty categories array', () => { + const task = new Task( + 'test-id', + 'Test Title', + 'Test Description', + [] + ); + + expect(task.categories).toEqual([]); + }); +}); diff --git a/libs/tasks/domain/src/lib/entities/task.entity.ts b/libs/tasks/domain/src/lib/entities/task.entity.ts new file mode 100644 index 0000000..430a6d5 --- /dev/null +++ b/libs/tasks/domain/src/lib/entities/task.entity.ts @@ -0,0 +1,8 @@ +export class Task { + constructor( + public readonly id: string, + public readonly title: string, + public readonly description: string | null, + public readonly categories: string[], + ) {} +} diff --git a/libs/tasks/domain/src/lib/entities/user-task.entity.spec.ts b/libs/tasks/domain/src/lib/entities/user-task.entity.spec.ts new file mode 100644 index 0000000..c02282d --- /dev/null +++ b/libs/tasks/domain/src/lib/entities/user-task.entity.spec.ts @@ -0,0 +1,30 @@ +import { User } from '@users/domain'; +import { Task } from './task.entity'; +import { UserTask } from './user-task.entity'; + +describe('UserTask', () => { + it('should create a user task instance', () => { + const task = new Task('task-1', 'Test Task', 'Description', ['category1']); + const user = new User('user-1', 'test@example.com', null, null); + const now = new Date(); + + const userTask = new UserTask( + 'user-task-1', + now, + null, + 'task-1', + task, + 'user-1', + user, + ); + + expect(userTask).toBeDefined(); + expect(userTask.id).toBe('user-task-1'); + expect(userTask.createdAt).toBe(now); + expect(userTask.updatedAt).toBeNull(); + expect(userTask.taskId).toBe('task-1'); + expect(userTask.task).toBe(task); + expect(userTask.userId).toBe('user-1'); + expect(userTask.user).toBe(user); + }); +}); diff --git a/libs/tasks/domain/src/lib/entities/user-task.entity.ts b/libs/tasks/domain/src/lib/entities/user-task.entity.ts new file mode 100644 index 0000000..de62bf6 --- /dev/null +++ b/libs/tasks/domain/src/lib/entities/user-task.entity.ts @@ -0,0 +1,15 @@ +import { User } from '@users/domain'; + +import { Task } from './task.entity'; + +export class UserTask { + constructor( + public readonly id: string, + public readonly createdAt: Date, + public readonly updatedAt: Date | null, + public readonly taskId: string, + public readonly task: Task | null, + public readonly userId: string, + public readonly user: User | null, + ) {} +} diff --git a/libs/tasks/domain/src/lib/task.repository.spec.ts b/libs/tasks/domain/src/lib/task.repository.spec.ts new file mode 100644 index 0000000..9dd5634 --- /dev/null +++ b/libs/tasks/domain/src/lib/task.repository.spec.ts @@ -0,0 +1,76 @@ +import { Task } from './entities/task.entity'; +import { UserTask } from './entities/user-task.entity'; +import { User } from '@users/domain'; +import { TaskRepository } from './task.repository'; + +describe('TaskRepository', () => { + let mockTaskRepository: TaskRepository; + let task: Task; + let user: User; + let userTask: UserTask; + + beforeEach(() => { + task = new Task('task-1', 'Test Task', 'Description', ['category1']); + user = new User('user-1', 'test@example.com', null, null); + userTask = new UserTask( + 'user-task-1', + new Date(), + null, + task.id, + task, + user.id, + user + ); + + mockTaskRepository = { + findAllTasks: jest.fn(), + findUserTasks: jest.fn(), + createUserTasks: jest.fn(), + updateUserTasks: jest.fn() + }; + }); + + describe('findAllTasks', () => { + it('should return all tasks', async () => { + const tasks = [task]; + (mockTaskRepository.findAllTasks as jest.Mock).mockResolvedValue(tasks); + + const result = await mockTaskRepository.findAllTasks(); + + expect(result).toEqual(tasks); + expect(mockTaskRepository.findAllTasks).toHaveBeenCalled(); + }); + }); + + describe('findUserTasks', () => { + it('should return tasks for a specific user', async () => { + const tasks = [task]; + (mockTaskRepository.findUserTasks as jest.Mock).mockResolvedValue(tasks); + + const result = await mockTaskRepository.findUserTasks(user.id); + + expect(result).toEqual(tasks); + expect(mockTaskRepository.findUserTasks).toHaveBeenCalledWith(user.id); + }); + }); + + describe('createUserTasks', () => { + it('should create user tasks', async () => { + const tasks = [task]; + + await mockTaskRepository.createUserTasks(user.id, tasks); + + expect(mockTaskRepository.createUserTasks).toHaveBeenCalledWith(user.id, tasks); + }); + }); + + describe('updateUserTasks', () => { + it('should update user tasks', async () => { + const userTasks = [userTask]; + + await mockTaskRepository.updateUserTasks(user.id, userTasks); + + expect(mockTaskRepository.updateUserTasks).toHaveBeenCalledWith(user.id, userTasks); + }); + }); +}); diff --git a/libs/tasks/domain/src/lib/task.repository.ts b/libs/tasks/domain/src/lib/task.repository.ts new file mode 100644 index 0000000..c5f2660 --- /dev/null +++ b/libs/tasks/domain/src/lib/task.repository.ts @@ -0,0 +1,9 @@ +import { Task } from './entities/task.entity'; +import { UserTask } from './entities/user-task.entity'; + +export interface TaskRepository { + findAllTasks(): Promise; + findUserTasks(userId: string): Promise; + createUserTasks(userId: string, tasks: Task[]): Promise; + updateUserTasks(userId: string, userTasks: UserTask[]): Promise; +} diff --git a/libs/tasks/domain/src/lib/tasks-domain.spec.ts b/libs/tasks/domain/src/lib/tasks-domain.spec.ts deleted file mode 100644 index f6e92b2..0000000 --- a/libs/tasks/domain/src/lib/tasks-domain.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { tasksDomain } from './tasks-domain'; - -describe('tasksDomain', () => { - it('should work', () => { - expect(tasksDomain()).toEqual('tasks-domain'); - }); -}); diff --git a/libs/tasks/domain/src/lib/tasks-domain.ts b/libs/tasks/domain/src/lib/tasks-domain.ts deleted file mode 100644 index f474aea..0000000 --- a/libs/tasks/domain/src/lib/tasks-domain.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function tasksDomain(): string { - return 'tasks-domain'; -} From 0732c1a85e0611512d70ed48c1807c47b898fe1d Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 14:01:24 +0900 Subject: [PATCH 03/22] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20mock=20TasksSer?= =?UTF-8?q?vice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/src/lib/tasks-application.spec.ts | 7 ------- libs/tasks/application/src/lib/tasks-application.ts | 3 --- libs/tasks/application/src/lib/tasks.service.ts | 12 ++++++++++++ 3 files changed, 12 insertions(+), 10 deletions(-) delete mode 100644 libs/tasks/application/src/lib/tasks-application.spec.ts delete mode 100644 libs/tasks/application/src/lib/tasks-application.ts create mode 100644 libs/tasks/application/src/lib/tasks.service.ts diff --git a/libs/tasks/application/src/lib/tasks-application.spec.ts b/libs/tasks/application/src/lib/tasks-application.spec.ts deleted file mode 100644 index 2640f80..0000000 --- a/libs/tasks/application/src/lib/tasks-application.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { tasksApplication } from './tasks-application'; - -describe('tasksApplication', () => { - it('should work', () => { - expect(tasksApplication()).toEqual('tasks-application'); - }); -}); diff --git a/libs/tasks/application/src/lib/tasks-application.ts b/libs/tasks/application/src/lib/tasks-application.ts deleted file mode 100644 index 75e4849..0000000 --- a/libs/tasks/application/src/lib/tasks-application.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function tasksApplication(): string { - return 'tasks-application'; -} diff --git a/libs/tasks/application/src/lib/tasks.service.ts b/libs/tasks/application/src/lib/tasks.service.ts new file mode 100644 index 0000000..30a88b4 --- /dev/null +++ b/libs/tasks/application/src/lib/tasks.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@nestjs/common'; +import { Task } from '@tasks/domain'; + +@Injectable() +export class TasksService { + constructor() {} + + async findAll(): Promise { + // TODO: Implement this + return []; + } +} From c483610b386b34ddd3616ef20d4c9f1bd88fa057 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 21:34:37 +0900 Subject: [PATCH 04/22] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20hard=20code=20u?= =?UTF-8?q?sers=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/application/src/index.ts | 2 +- .../application/src/lib/tasks.service.spec.ts | 44 +++++++++++++++++++ .../application/src/lib/tasks.service.ts | 15 ++++++- 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 libs/tasks/application/src/lib/tasks.service.spec.ts diff --git a/libs/tasks/application/src/index.ts b/libs/tasks/application/src/index.ts index e9187da..7465cca 100644 --- a/libs/tasks/application/src/index.ts +++ b/libs/tasks/application/src/index.ts @@ -1 +1 @@ -export * from './lib/tasks-application'; +export * from './lib/tasks.service'; diff --git a/libs/tasks/application/src/lib/tasks.service.spec.ts b/libs/tasks/application/src/lib/tasks.service.spec.ts new file mode 100644 index 0000000..beb5963 --- /dev/null +++ b/libs/tasks/application/src/lib/tasks.service.spec.ts @@ -0,0 +1,44 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { Task, UserTask } from '@tasks/domain'; +import { TasksService } from './tasks.service'; + +describe('TasksService', () => { + let service: TasksService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [TasksService], + }).compile(); + + service = module.get(TasksService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('findAll', () => { + it('should return an array of tasks', async () => { + const result = await service.findAll(); + expect(Array.isArray(result)).toBe(true); + }); + }); + + describe('createUserTasks', () => { + it('should create user tasks and return success', async () => { + const userId = '123'; + const tasks: Task[] = []; + const result = await service.createUserTasks(userId, tasks); + expect(result).toBe('success'); + }); + }); + + describe('updateUserTasks', () => { + it('should update user tasks and return success', async () => { + const userId = '123'; + const userTasks: UserTask[] = []; + const result = await service.updateUserTasks(userId, userTasks); + expect(result).toBe('success'); + }); + }); +}); diff --git a/libs/tasks/application/src/lib/tasks.service.ts b/libs/tasks/application/src/lib/tasks.service.ts index 30a88b4..76b839e 100644 --- a/libs/tasks/application/src/lib/tasks.service.ts +++ b/libs/tasks/application/src/lib/tasks.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { Task } from '@tasks/domain'; +import { Task, UserTask } from '@tasks/domain'; @Injectable() export class TasksService { @@ -9,4 +9,17 @@ export class TasksService { // TODO: Implement this return []; } + + async createUserTasks(userId: string, tasks: Task[]): Promise { + // TODO: Implement this + return 'success'; + } + + async updateUserTasks( + userId: string, + userTasks: UserTask[], + ): Promise { + // TODO: Implement this + return 'success'; + } } From d58a7082fe7ecb4c926cc6f86f0e7515801c0cde Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 22:02:22 +0900 Subject: [PATCH 05/22] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20findUserTasks?= =?UTF-8?q?=20and=20fix=20create/update=20user=20tasks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/src/lib/tasks.service.spec.ts | 18 +++++++++++++++--- .../tasks/application/src/lib/tasks.service.ts | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/libs/tasks/application/src/lib/tasks.service.spec.ts b/libs/tasks/application/src/lib/tasks.service.spec.ts index beb5963..e151139 100644 --- a/libs/tasks/application/src/lib/tasks.service.spec.ts +++ b/libs/tasks/application/src/lib/tasks.service.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { Task, UserTask } from '@tasks/domain'; + import { TasksService } from './tasks.service'; describe('TasksService', () => { @@ -24,10 +24,17 @@ describe('TasksService', () => { }); }); + describe('findUserTasks', () => { + it('should return an array of user tasks', async () => { + const result = await service.findUserTasks('123'); + expect(Array.isArray(result)).toBe(true); + }); + }); + describe('createUserTasks', () => { it('should create user tasks and return success', async () => { const userId = '123'; - const tasks: Task[] = []; + const tasks = [{ id: '3', createdAt: new Date() }]; const result = await service.createUserTasks(userId, tasks); expect(result).toBe('success'); }); @@ -36,7 +43,12 @@ describe('TasksService', () => { describe('updateUserTasks', () => { it('should update user tasks and return success', async () => { const userId = '123'; - const userTasks: UserTask[] = []; + const userTasks = [ + { + id: 'user-task-1', + updatedAt: new Date(), + }, + ]; const result = await service.updateUserTasks(userId, userTasks); expect(result).toBe('success'); }); diff --git a/libs/tasks/application/src/lib/tasks.service.ts b/libs/tasks/application/src/lib/tasks.service.ts index 76b839e..9ceb1f6 100644 --- a/libs/tasks/application/src/lib/tasks.service.ts +++ b/libs/tasks/application/src/lib/tasks.service.ts @@ -10,14 +10,25 @@ export class TasksService { return []; } - async createUserTasks(userId: string, tasks: Task[]): Promise { + async findUserTasks( + userId: string, + range?: { from: Date; to: Date }, + ): Promise { + // TODO: Implement this + return []; + } + + async createUserTasks( + userId: string, + tasks: { id: string; createdAt: Date }[], + ): Promise { // TODO: Implement this return 'success'; } async updateUserTasks( userId: string, - userTasks: UserTask[], + userTasks: { id: string; updatedAt: Date }[], ): Promise { // TODO: Implement this return 'success'; From 48646b7e1fe0b999226bf5c0d46efa39e89b54c1 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 22:03:43 +0900 Subject: [PATCH 06/22] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20TaskRepositor?= =?UTF-8?q?y=20definition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/domain/src/lib/task.repository.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/libs/tasks/domain/src/lib/task.repository.ts b/libs/tasks/domain/src/lib/task.repository.ts index c5f2660..660b93e 100644 --- a/libs/tasks/domain/src/lib/task.repository.ts +++ b/libs/tasks/domain/src/lib/task.repository.ts @@ -3,7 +3,16 @@ import { UserTask } from './entities/user-task.entity'; export interface TaskRepository { findAllTasks(): Promise; - findUserTasks(userId: string): Promise; - createUserTasks(userId: string, tasks: Task[]): Promise; - updateUserTasks(userId: string, userTasks: UserTask[]): Promise; + findUserTasks( + userId: string, + range?: { from: Date; to: Date }, + ): Promise; + createUserTasks( + userId: string, + tasks: { id: string; createdAt: Date }[], + ): Promise; + updateUserTasks( + userId: string, + userTasks: { id: string; updatedAt: Date }[], + ): Promise; } From 934ee653c165049a4d86d096c9e99c72767fe4ba Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 22:09:11 +0900 Subject: [PATCH 07/22] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20task=20resolver?= =?UTF-8?q?s=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/interface-adapters/src/index.ts | 2 +- .../src/lib/dto/create-user-task.dto.spec.ts | 14 ++++++++ .../src/lib/dto/create-user-task.dto.ts | 15 +++++++++ .../src/lib/dto/task.dto.spec.ts | 29 +++++++++++++++++ .../src/lib/dto/task.dto.ts | 30 +++++++++++++++++ .../src/lib/dto/update-user-task.dto.spec.ts | 14 ++++++++ .../src/lib/dto/update-user-task.dto.ts | 15 +++++++++ .../src/lib/resolver/tasks.resolver.spec.ts | 32 +++++++++++++++++++ .../src/lib/resolver/tasks.resolver.ts | 32 +++++++++++++++++++ .../src/lib/tasks-interface-adapters.spec.ts | 7 ---- .../src/lib/tasks-interface-adapters.ts | 3 -- .../src/lib/tasks.module.ts | 11 +++++++ 12 files changed, 193 insertions(+), 11 deletions(-) create mode 100644 libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.spec.ts create mode 100644 libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts create mode 100644 libs/tasks/interface-adapters/src/lib/dto/task.dto.spec.ts create mode 100644 libs/tasks/interface-adapters/src/lib/dto/task.dto.ts create mode 100644 libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.spec.ts create mode 100644 libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts create mode 100644 libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts create mode 100644 libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts delete mode 100644 libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.spec.ts delete mode 100644 libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.ts create mode 100644 libs/tasks/interface-adapters/src/lib/tasks.module.ts diff --git a/libs/tasks/interface-adapters/src/index.ts b/libs/tasks/interface-adapters/src/index.ts index a6214aa..c92ed5c 100644 --- a/libs/tasks/interface-adapters/src/index.ts +++ b/libs/tasks/interface-adapters/src/index.ts @@ -1 +1 @@ -export * from './lib/tasks-interface-adapters'; +export * from './lib/tasks.module'; \ No newline at end of file diff --git a/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.spec.ts b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.spec.ts new file mode 100644 index 0000000..099aa9b --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.spec.ts @@ -0,0 +1,14 @@ +import { CreateUserTaskDto } from './create-user-task.dto'; + +describe('CreateUserTaskDto', () => { + it('should create a CreateUserTaskDto instance', () => { + const id = '123'; + const createdAt = new Date(); + + const dto = new CreateUserTaskDto(id, createdAt); + + expect(dto).toBeDefined(); + expect(dto.id).toBe(id); + expect(dto.createdAt).toBe(createdAt); + }); +}); diff --git a/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts new file mode 100644 index 0000000..6534969 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts @@ -0,0 +1,15 @@ +import { Field, InputType } from '@nestjs/graphql'; + +@InputType() +export class CreateUserTaskDto { + @Field(() => String) + id: string; + + @Field(() => Date) + createdAt: Date; + + constructor(id: string, createdAt: Date) { + this.id = id; + this.createdAt = createdAt; + } +} diff --git a/libs/tasks/interface-adapters/src/lib/dto/task.dto.spec.ts b/libs/tasks/interface-adapters/src/lib/dto/task.dto.spec.ts new file mode 100644 index 0000000..cd748dc --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/task.dto.spec.ts @@ -0,0 +1,29 @@ +import { TaskDto } from './task.dto'; + +describe('TaskDto', () => { + it('should create a TaskDto instance', () => { + const id = '123'; + const title = 'Test Task'; + const description = 'Test Description'; + const categories = ['category1', 'category2']; + + const dto = new TaskDto(id, title, description, categories); + + expect(dto).toBeDefined(); + expect(dto.id).toBe(id); + expect(dto.title).toBe(title); + expect(dto.description).toBe(description); + expect(dto.categories).toEqual(categories); + }); + + it('should allow null description', () => { + const id = '123'; + const title = 'Test Task'; + const description = null; + const categories = ['category1']; + + const dto = new TaskDto(id, title, description, categories); + + expect(dto.description).toBeNull(); + }); +}); diff --git a/libs/tasks/interface-adapters/src/lib/dto/task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/task.dto.ts new file mode 100644 index 0000000..a997573 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/task.dto.ts @@ -0,0 +1,30 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; +import { IsOptional } from 'class-validator'; + +@ObjectType() +export class TaskDto { + @Field(() => ID) + id: string; + + @Field() + title: string; + + @Field(() => String, { nullable: true }) + @IsOptional() + description: string | null; + + @Field(() => [String]) + categories: string[]; + + constructor( + id: string, + title: string, + description: string | null, + categories: string[], + ) { + this.id = id; + this.title = title; + this.description = description; + this.categories = categories; + } +} diff --git a/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.spec.ts b/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.spec.ts new file mode 100644 index 0000000..2019dd1 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.spec.ts @@ -0,0 +1,14 @@ +import { UpdateUserTaskDto } from './update-user-task.dto'; + +describe('UpdateUserTaskDto', () => { + it('should create an UpdateUserTaskDto instance', () => { + const id = '123'; + const updatedAt = new Date(); + + const dto = new UpdateUserTaskDto(id, updatedAt); + + expect(dto).toBeDefined(); + expect(dto.id).toBe(id); + expect(dto.updatedAt).toBe(updatedAt); + }); +}); diff --git a/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts new file mode 100644 index 0000000..1be5004 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts @@ -0,0 +1,15 @@ +import { Field, InputType } from '@nestjs/graphql'; + +@InputType() +export class UpdateUserTaskDto { + @Field(() => String) + id: string; + + @Field(() => Date) + updatedAt: Date; + + constructor(id: string, updatedAt: Date) { + this.id = id; + this.updatedAt = updatedAt; + } +} diff --git a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts new file mode 100644 index 0000000..22f80b3 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts @@ -0,0 +1,32 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { TasksService } from '@tasks/application'; + +import { TaskDto } from '../dto/task.dto'; +import { CreateUserTaskDto } from '../dto/create-user-task.dto'; +import { UpdateUserTaskDto } from '../dto/update-user-task.dto'; + +@Resolver(() => TaskDto) +export class TasksResolver { + constructor(private tasksService: TasksService) {} + + @Query(() => TaskDto, { nullable: true }) + async getTasks(): Promise { + return this.tasksService.findAll(); + } + + @Mutation(() => String) + async createUserTasks( + @Args('userId') userId: string, + @Args('tasks') tasks: CreateUserTaskDto[], + ): Promise { + return this.tasksService.createUserTasks(userId, tasks); + } + + @Mutation(() => String) + async updateUserTasks( + @Args('userId') userId: string, + @Args('userTasks') userTasks: UpdateUserTaskDto[], + ): Promise { + return this.tasksService.updateUserTasks(userId, userTasks); + } +} diff --git a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts new file mode 100644 index 0000000..22f80b3 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts @@ -0,0 +1,32 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { TasksService } from '@tasks/application'; + +import { TaskDto } from '../dto/task.dto'; +import { CreateUserTaskDto } from '../dto/create-user-task.dto'; +import { UpdateUserTaskDto } from '../dto/update-user-task.dto'; + +@Resolver(() => TaskDto) +export class TasksResolver { + constructor(private tasksService: TasksService) {} + + @Query(() => TaskDto, { nullable: true }) + async getTasks(): Promise { + return this.tasksService.findAll(); + } + + @Mutation(() => String) + async createUserTasks( + @Args('userId') userId: string, + @Args('tasks') tasks: CreateUserTaskDto[], + ): Promise { + return this.tasksService.createUserTasks(userId, tasks); + } + + @Mutation(() => String) + async updateUserTasks( + @Args('userId') userId: string, + @Args('userTasks') userTasks: UpdateUserTaskDto[], + ): Promise { + return this.tasksService.updateUserTasks(userId, userTasks); + } +} diff --git a/libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.spec.ts b/libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.spec.ts deleted file mode 100644 index a33f1f3..0000000 --- a/libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { tasksInterfaceAdapters } from './tasks-interface-adapters'; - -describe('tasksInterfaceAdapters', () => { - it('should work', () => { - expect(tasksInterfaceAdapters()).toEqual('tasks-interface-adapters'); - }); -}); diff --git a/libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.ts b/libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.ts deleted file mode 100644 index 91f284e..0000000 --- a/libs/tasks/interface-adapters/src/lib/tasks-interface-adapters.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function tasksInterfaceAdapters(): string { - return 'tasks-interface-adapters'; -} diff --git a/libs/tasks/interface-adapters/src/lib/tasks.module.ts b/libs/tasks/interface-adapters/src/lib/tasks.module.ts new file mode 100644 index 0000000..8121d73 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/tasks.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TasksService } from '@tasks/application'; + +import { TasksResolver } from './resolver/tasks.resolver'; + +@Module({ + imports: [], + providers: [TasksResolver, TasksService], + exports: [TasksService], +}) +export class TasksModule {} From a135c0fd2431e3469a6243b0c879a3b3613413b7 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 22:09:32 +0900 Subject: [PATCH 08/22] =?UTF-8?q?test:=20=F0=9F=A7=AA=20test=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/domain/src/lib/task.repository.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/tasks/domain/src/lib/task.repository.spec.ts b/libs/tasks/domain/src/lib/task.repository.spec.ts index 9dd5634..7aa87b0 100644 --- a/libs/tasks/domain/src/lib/task.repository.spec.ts +++ b/libs/tasks/domain/src/lib/task.repository.spec.ts @@ -56,7 +56,10 @@ describe('TaskRepository', () => { describe('createUserTasks', () => { it('should create user tasks', async () => { - const tasks = [task]; + const tasks = [{ + id: 'task-1', + createdAt: new Date() + }]; await mockTaskRepository.createUserTasks(user.id, tasks); @@ -66,7 +69,10 @@ describe('TaskRepository', () => { describe('updateUserTasks', () => { it('should update user tasks', async () => { - const userTasks = [userTask]; + const userTasks = [{ + id: 'user-task-1', + updatedAt: new Date() + }]; await mockTaskRepository.updateUserTasks(user.id, userTasks); From a40db58045f985426d1e113a6d95370f08f7fb5e Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 22:33:42 +0900 Subject: [PATCH 09/22] =?UTF-8?q?fix:=20=F0=9F=90=9B=20delete=20empty=20co?= =?UTF-8?q?nstructor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/application/src/lib/tasks.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/tasks/application/src/lib/tasks.service.ts b/libs/tasks/application/src/lib/tasks.service.ts index 9ceb1f6..699f457 100644 --- a/libs/tasks/application/src/lib/tasks.service.ts +++ b/libs/tasks/application/src/lib/tasks.service.ts @@ -3,8 +3,6 @@ import { Task, UserTask } from '@tasks/domain'; @Injectable() export class TasksService { - constructor() {} - async findAll(): Promise { // TODO: Implement this return []; From 94fbc9cadfce2366dbc4299cc22a2d63f773193e Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 22:54:41 +0900 Subject: [PATCH 10/22] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20add=20Us?= =?UTF-8?q?erTasksService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/application/src/index.ts | 1 + .../application/src/lib/tasks.service.spec.ts | 30 -------------- .../application/src/lib/tasks.service.ts | 26 +------------ .../src/lib/user-tasks.service.spec.ts | 39 +++++++++++++++++++ .../application/src/lib/user-tasks.service.ts | 29 ++++++++++++++ 5 files changed, 70 insertions(+), 55 deletions(-) create mode 100644 libs/tasks/application/src/lib/user-tasks.service.spec.ts create mode 100644 libs/tasks/application/src/lib/user-tasks.service.ts diff --git a/libs/tasks/application/src/index.ts b/libs/tasks/application/src/index.ts index 7465cca..05a5430 100644 --- a/libs/tasks/application/src/index.ts +++ b/libs/tasks/application/src/index.ts @@ -1 +1,2 @@ export * from './lib/tasks.service'; +export * from './lib/user-tasks.service'; \ No newline at end of file diff --git a/libs/tasks/application/src/lib/tasks.service.spec.ts b/libs/tasks/application/src/lib/tasks.service.spec.ts index e151139..95996af 100644 --- a/libs/tasks/application/src/lib/tasks.service.spec.ts +++ b/libs/tasks/application/src/lib/tasks.service.spec.ts @@ -23,34 +23,4 @@ describe('TasksService', () => { expect(Array.isArray(result)).toBe(true); }); }); - - describe('findUserTasks', () => { - it('should return an array of user tasks', async () => { - const result = await service.findUserTasks('123'); - expect(Array.isArray(result)).toBe(true); - }); - }); - - describe('createUserTasks', () => { - it('should create user tasks and return success', async () => { - const userId = '123'; - const tasks = [{ id: '3', createdAt: new Date() }]; - const result = await service.createUserTasks(userId, tasks); - expect(result).toBe('success'); - }); - }); - - describe('updateUserTasks', () => { - it('should update user tasks and return success', async () => { - const userId = '123'; - const userTasks = [ - { - id: 'user-task-1', - updatedAt: new Date(), - }, - ]; - const result = await service.updateUserTasks(userId, userTasks); - expect(result).toBe('success'); - }); - }); }); diff --git a/libs/tasks/application/src/lib/tasks.service.ts b/libs/tasks/application/src/lib/tasks.service.ts index 699f457..4ae8c08 100644 --- a/libs/tasks/application/src/lib/tasks.service.ts +++ b/libs/tasks/application/src/lib/tasks.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { Task, UserTask } from '@tasks/domain'; +import { Task } from '@tasks/domain'; @Injectable() export class TasksService { @@ -7,28 +7,4 @@ export class TasksService { // TODO: Implement this return []; } - - async findUserTasks( - userId: string, - range?: { from: Date; to: Date }, - ): Promise { - // TODO: Implement this - return []; - } - - async createUserTasks( - userId: string, - tasks: { id: string; createdAt: Date }[], - ): Promise { - // TODO: Implement this - return 'success'; - } - - async updateUserTasks( - userId: string, - userTasks: { id: string; updatedAt: Date }[], - ): Promise { - // TODO: Implement this - return 'success'; - } } diff --git a/libs/tasks/application/src/lib/user-tasks.service.spec.ts b/libs/tasks/application/src/lib/user-tasks.service.spec.ts new file mode 100644 index 0000000..b43230e --- /dev/null +++ b/libs/tasks/application/src/lib/user-tasks.service.spec.ts @@ -0,0 +1,39 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UserTasksService } from './user-tasks.service'; + +describe('UserTasksService', () => { + let service: UserTasksService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UserTasksService], + }).compile(); + + service = module.get(UserTasksService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('findMany', () => { + it('should return user tasks', async () => { + const result = await service.findMany('userId', { from: new Date(), to: new Date() }); + expect(result).toEqual([]); + }); + }); + + describe('createSome', () => { + it('should create user tasks', async () => { + const result = await service.createSome('userId', [{ id: '1', createdAt: new Date() }]); + expect(result).toEqual('success'); + }); + }); + + describe('updateSome', () => { + it('should update user tasks', async () => { + const result = await service.updateSome('userId', [{ id: '1', updatedAt: new Date() }]); + expect(result).toEqual('success'); + }); + }); +}); diff --git a/libs/tasks/application/src/lib/user-tasks.service.ts b/libs/tasks/application/src/lib/user-tasks.service.ts new file mode 100644 index 0000000..d46d5ed --- /dev/null +++ b/libs/tasks/application/src/lib/user-tasks.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { UserTask } from '@tasks/domain'; + +@Injectable() +export class UserTasksService { + async findMany( + userId: string, + range?: { from: Date; to: Date }, + ): Promise { + // TODO: Implement this + return []; + } + + async createSome( + userId: string, + tasks: { id: string; createdAt: Date }[], + ): Promise { + // TODO: Implement this + return 'success'; + } + + async updateSome( + userId: string, + userTasks: { id: string; updatedAt: Date }[], + ): Promise { + // TODO: Implement this + return 'success'; + } +} From 54f05226ee6478b6706aa3c08bc0c98b49a445bc Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 23:09:45 +0900 Subject: [PATCH 11/22] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20add=20Us?= =?UTF-8?q?erTasksResolver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/dto/user-task.dto.spec.ts | 61 ++++++++++++++++++ .../src/lib/dto/user-task.dto.ts | 46 ++++++++++++++ .../src/lib/resolver/tasks.resolver.spec.ts | 60 ++++++++++-------- .../src/lib/resolver/tasks.resolver.ts | 26 ++------ .../lib/resolver/user-tasks.resolver.spec.ts | 62 +++++++++++++++++++ .../src/lib/resolver/user-tasks.resolver.ts | 37 +++++++++++ .../src/lib/tasks.module.ts | 7 ++- 7 files changed, 249 insertions(+), 50 deletions(-) create mode 100644 libs/tasks/interface-adapters/src/lib/dto/user-task.dto.spec.ts create mode 100644 libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts create mode 100644 libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts create mode 100644 libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts diff --git a/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.spec.ts b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.spec.ts new file mode 100644 index 0000000..a5b1066 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.spec.ts @@ -0,0 +1,61 @@ +import { UserTaskDto } from './user-task.dto'; +import { TaskDto } from './task.dto'; +import { UserDto } from '@users/interface-adapters'; + +describe('UserTaskDto', () => { + it('should create a UserTaskDto instance', () => { + const id = '123'; + const createdAt = new Date(); + const updatedAt = new Date(); + const taskId = '456'; + const task = new TaskDto('456', 'Test Task', 'Test Description', []); + const userId = '789'; + const user = new UserDto('789', 'test@example.com', null, null); + + const userTaskDto = new UserTaskDto( + id, + createdAt, + updatedAt, + taskId, + task, + userId, + user, + ); + + expect(userTaskDto.id).toBe(id); + expect(userTaskDto.createdAt).toBe(createdAt); + expect(userTaskDto.updatedAt).toBe(updatedAt); + expect(userTaskDto.taskId).toBe(taskId); + expect(userTaskDto.task).toBe(task); + expect(userTaskDto.userId).toBe(userId); + expect(userTaskDto.user).toBe(user); + }); + + it('should create a UserTaskDto instance with null values', () => { + const id = '123'; + const createdAt = new Date(); + const updatedAt = null; + const taskId = '456'; + const task = null; + const userId = '789'; + const user = null; + + const userTaskDto = new UserTaskDto( + id, + createdAt, + updatedAt, + taskId, + task, + userId, + user, + ); + + expect(userTaskDto.id).toBe(id); + expect(userTaskDto.createdAt).toBe(createdAt); + expect(userTaskDto.updatedAt).toBeNull(); + expect(userTaskDto.taskId).toBe(taskId); + expect(userTaskDto.task).toBeNull(); + expect(userTaskDto.userId).toBe(userId); + expect(userTaskDto.user).toBeNull(); + }); +}); diff --git a/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts new file mode 100644 index 0000000..b70a14a --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts @@ -0,0 +1,46 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; +import { UserDto } from '@users/interface-adapters'; + +import { TaskDto } from './task.dto'; + +@ObjectType() +export class UserTaskDto { + @Field(() => ID) + id: string; + + @Field(() => Date) + createdAt: Date; + + @Field(() => Date, { nullable: true }) + updatedAt: Date | null; + + @Field(() => String) + taskId: String; + + @Field(() => TaskDto, { nullable: true }) + task: TaskDto | null; + + @Field(() => String) + userId: string; + + @Field(() => UserDto, { nullable: true }) + user: UserDto | null; + + constructor( + id: string, + createdAt: Date, + updatedAt: Date | null, + taskId: string, + task: TaskDto | null, + userId: string, + user: UserDto | null, + ) { + this.id = id; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.taskId = taskId; + this.task = task; + this.userId = userId; + this.user = user; + } +} diff --git a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts index 22f80b3..18ef595 100644 --- a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts +++ b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts @@ -1,32 +1,40 @@ -import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { Test, TestingModule } from '@nestjs/testing'; import { TasksService } from '@tasks/application'; -import { TaskDto } from '../dto/task.dto'; -import { CreateUserTaskDto } from '../dto/create-user-task.dto'; -import { UpdateUserTaskDto } from '../dto/update-user-task.dto'; +import { TasksResolver } from './tasks.resolver'; -@Resolver(() => TaskDto) -export class TasksResolver { - constructor(private tasksService: TasksService) {} +describe('TasksResolver', () => { + let resolver: TasksResolver; + let service: TasksService; - @Query(() => TaskDto, { nullable: true }) - async getTasks(): Promise { - return this.tasksService.findAll(); - } + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + TasksResolver, + { + provide: TasksService, + useValue: { + findAll: jest.fn(), + findUserTasks: jest.fn(), + createUserTasks: jest.fn(), + updateUserTasks: jest.fn() + }, + }, + ], + }).compile(); - @Mutation(() => String) - async createUserTasks( - @Args('userId') userId: string, - @Args('tasks') tasks: CreateUserTaskDto[], - ): Promise { - return this.tasksService.createUserTasks(userId, tasks); - } + resolver = module.get(TasksResolver); + service = module.get(TasksService); + }); - @Mutation(() => String) - async updateUserTasks( - @Args('userId') userId: string, - @Args('userTasks') userTasks: UpdateUserTaskDto[], - ): Promise { - return this.tasksService.updateUserTasks(userId, userTasks); - } -} + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); + + describe('getTasks', () => { + it('should return all tasks', async () => { + const result = await resolver.getTasks(); + expect(result).toEqual([]); + }); + }); +}); diff --git a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts index 22f80b3..d25f1a0 100644 --- a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts +++ b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.ts @@ -1,32 +1,16 @@ -import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { Query, Resolver } from '@nestjs/graphql'; import { TasksService } from '@tasks/application'; import { TaskDto } from '../dto/task.dto'; -import { CreateUserTaskDto } from '../dto/create-user-task.dto'; -import { UpdateUserTaskDto } from '../dto/update-user-task.dto'; +import { Injectable } from '@nestjs/common'; -@Resolver(() => TaskDto) +@Injectable() +@Resolver(() => [TaskDto]) export class TasksResolver { constructor(private tasksService: TasksService) {} - @Query(() => TaskDto, { nullable: true }) + @Query(() => [TaskDto]) async getTasks(): Promise { return this.tasksService.findAll(); } - - @Mutation(() => String) - async createUserTasks( - @Args('userId') userId: string, - @Args('tasks') tasks: CreateUserTaskDto[], - ): Promise { - return this.tasksService.createUserTasks(userId, tasks); - } - - @Mutation(() => String) - async updateUserTasks( - @Args('userId') userId: string, - @Args('userTasks') userTasks: UpdateUserTaskDto[], - ): Promise { - return this.tasksService.updateUserTasks(userId, userTasks); - } } diff --git a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts new file mode 100644 index 0000000..045204b --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts @@ -0,0 +1,62 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UserTasksService } from '@tasks/application'; + +import { UserTasksResolver } from './user-tasks.resolver'; + +describe('UserTasksResolver', () => { + let resolver: UserTasksResolver; + let service: UserTasksService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + UserTasksResolver, + { + provide: UserTasksService, + useValue: { + findMany: jest.fn(), + createSome: jest.fn(), + updateSome: jest.fn(), + }, + }, + ], + }).compile(); + + resolver = module.get(UserTasksResolver); + service = module.get(UserTasksService); + }); + + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); + + describe('findUserTasks', () => { + it('should return user tasks for given userId and range', async () => { + const userId = 'test-user-id'; + const range = { from: new Date(), to: new Date() }; + const result = await resolver.findUserTasks(userId, range); + expect(service.findMany).toHaveBeenCalledWith(userId, range); + expect(result).toEqual([]); + }); + }); + + describe('createUserTasks', () => { + it('should create user tasks', async () => { + const userId = 'test-user-id'; + const tasks = [{ id: 'task-1', createdAt: new Date() }]; + const result = await resolver.createUserTasks(userId, tasks); + expect(service.createSome).toHaveBeenCalledWith(userId, tasks); + expect(result).toBeDefined(); + }); + }); + + describe('updateUserTasks', () => { + it('should update user tasks', async () => { + const userId = 'test-user-id'; + const userTasks = [{ id: 'user-task-1', updatedAt: new Date() }]; + const result = await resolver.updateUserTasks(userId, userTasks); + expect(service.updateSome).toHaveBeenCalledWith(userId, userTasks); + expect(result).toBeDefined(); + }); + }); +}); diff --git a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts new file mode 100644 index 0000000..2e92a10 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts @@ -0,0 +1,37 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; +import { UserTasksService } from '@tasks/application'; + +import { CreateUserTaskDto } from '../dto/create-user-task.dto'; +import { UpdateUserTaskDto } from '../dto/update-user-task.dto'; +import { UserTaskDto } from '../dto/user-task.dto'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +@Resolver(() => [UserTaskDto]) +export class UserTasksResolver { + constructor(private userTasksService: UserTasksService) {} + + @Query(() => [UserTaskDto]) + async findUserTasks( + @Args('userId') userId: string, + @Args('range') range: { from: Date; to: Date }, + ): Promise { + return this.userTasksService.findMany(userId, range); + } + + @Mutation(() => String) + async createUserTasks( + @Args('userId') userId: string, + @Args('tasks') tasks: CreateUserTaskDto[], + ): Promise { + return this.userTasksService.createSome(userId, tasks); + } + + @Mutation(() => String) + async updateUserTasks( + @Args('userId') userId: string, + @Args('userTasks') userTasks: UpdateUserTaskDto[], + ): Promise { + return this.userTasksService.updateSome(userId, userTasks); + } +} diff --git a/libs/tasks/interface-adapters/src/lib/tasks.module.ts b/libs/tasks/interface-adapters/src/lib/tasks.module.ts index 8121d73..0a59729 100644 --- a/libs/tasks/interface-adapters/src/lib/tasks.module.ts +++ b/libs/tasks/interface-adapters/src/lib/tasks.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; -import { TasksService } from '@tasks/application'; +import { TasksService, UserTasksService } from '@tasks/application'; import { TasksResolver } from './resolver/tasks.resolver'; +import { UserTasksResolver } from './resolver/user-tasks.resolver'; @Module({ imports: [], - providers: [TasksResolver, TasksService], - exports: [TasksService], + providers: [TasksResolver, TasksService, UserTasksResolver, UserTasksService], + exports: [TasksService, UserTasksService], }) export class TasksModule {} From ba44970c36acfcdc5d6003d9a197b1f301c7a11e Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Sat, 7 Dec 2024 23:13:58 +0900 Subject: [PATCH 12/22] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Prefer=20using=20th?= =?UTF-8?q?e=20primitive=20`string`=20as=20a=20type=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts index b70a14a..6325095 100644 --- a/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts +++ b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts @@ -15,7 +15,7 @@ export class UserTaskDto { updatedAt: Date | null; @Field(() => String) - taskId: String; + taskId: string; @Field(() => TaskDto, { nullable: true }) task: TaskDto | null; From 7ee395926f59ec69fba7fd514ff5b4cd96a5bdb2 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 09:49:21 +0900 Subject: [PATCH 13/22] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20validation=20fo?= =?UTF-8?q?r=20the=20id=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface-adapters/src/lib/dto/create-user-task.dto.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts index 6534969..16782f2 100644 --- a/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts +++ b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts @@ -1,8 +1,11 @@ import { Field, InputType } from '@nestjs/graphql'; +import { IsNotEmpty, IsUUID } from 'class-validator'; @InputType() export class CreateUserTaskDto { @Field(() => String) + @IsNotEmpty() + @IsUUID() id: string; @Field(() => Date) From 83df7bbd87f30f05afc593ece74b4bf622dbb09c Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 09:53:42 +0900 Subject: [PATCH 14/22] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20creating?= =?UTF-8?q?=20a=20base=20DTO=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/dto/base-task.dto.ts | 14 ++++++++++++++ .../src/lib/dto/create-user-task.dto.ts | 14 ++++++-------- .../src/lib/dto/update-user-task.dto.ts | 13 +++++++------ 3 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 libs/tasks/interface-adapters/src/lib/dto/base-task.dto.ts diff --git a/libs/tasks/interface-adapters/src/lib/dto/base-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/base-task.dto.ts new file mode 100644 index 0000000..1b40310 --- /dev/null +++ b/libs/tasks/interface-adapters/src/lib/dto/base-task.dto.ts @@ -0,0 +1,14 @@ +import { Field, InputType } from "@nestjs/graphql"; +import { IsNotEmpty, IsUUID } from "class-validator"; + +@InputType({ isAbstract: true }) +export abstract class BaseTaskDto { + @Field(() => String) + @IsNotEmpty() + @IsUUID() + id: string; + + constructor(id: string) { + this.id = id; + } +} \ No newline at end of file diff --git a/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts index 16782f2..50d6380 100644 --- a/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts +++ b/libs/tasks/interface-adapters/src/lib/dto/create-user-task.dto.ts @@ -1,18 +1,16 @@ import { Field, InputType } from '@nestjs/graphql'; import { IsNotEmpty, IsUUID } from 'class-validator'; -@InputType() -export class CreateUserTaskDto { - @Field(() => String) - @IsNotEmpty() - @IsUUID() - id: string; +import { BaseTaskDto } from './base-task.dto'; +@InputType({ description: 'Input type for creating a new user task' }) +export class CreateUserTaskDto extends BaseTaskDto { @Field(() => Date) - createdAt: Date; + @IsNotEmpty() + createdAt: Date = new Date(); constructor(id: string, createdAt: Date) { - this.id = id; + super(id); this.createdAt = createdAt; } } diff --git a/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts index 1be5004..000ad81 100644 --- a/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts +++ b/libs/tasks/interface-adapters/src/lib/dto/update-user-task.dto.ts @@ -1,15 +1,16 @@ import { Field, InputType } from '@nestjs/graphql'; +import { IsNotEmpty } from 'class-validator'; -@InputType() -export class UpdateUserTaskDto { - @Field(() => String) - id: string; +import { BaseTaskDto } from './base-task.dto'; +@InputType({ description: 'Input type for updating a user task' }) +export class UpdateUserTaskDto extends BaseTaskDto { @Field(() => Date) - updatedAt: Date; + @IsNotEmpty() + updatedAt: Date = new Date(); constructor(id: string, updatedAt: Date) { - this.id = id; + super(id); this.updatedAt = updatedAt; } } From 6a28280228b4d589ae48fd815f6880c98f0af58f Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 09:58:07 +0900 Subject: [PATCH 15/22] =?UTF-8?q?docs:=20=F0=9F=93=9D=20add=20method=20doc?= =?UTF-8?q?umentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/tasks/domain/src/lib/task.repository.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libs/tasks/domain/src/lib/task.repository.ts b/libs/tasks/domain/src/lib/task.repository.ts index 660b93e..bdbbc5b 100644 --- a/libs/tasks/domain/src/lib/task.repository.ts +++ b/libs/tasks/domain/src/lib/task.repository.ts @@ -1,16 +1,28 @@ import { Task } from './entities/task.entity'; -import { UserTask } from './entities/user-task.entity'; export interface TaskRepository { findAllTasks(): Promise; + findUserTasks( userId: string, range?: { from: Date; to: Date }, ): Promise; + + /** + * Creates tasks for a specific user + * @throws {TaskValidationError} If the tasks are invalid + * @throws {UserNotFoundError} If user doesn't exist + */ createUserTasks( userId: string, tasks: { id: string; createdAt: Date }[], ): Promise; + + /** + * Updates existing user tasks + * @throws {TaskNotFoundError} If any task doesn't exist + * @throws {UserNotFoundError} If user doesn't exist + */ updateUserTasks( userId: string, userTasks: { id: string; updatedAt: Date }[], From 2ef1c11cb73f97fdbe1f3323938a559abdf1df8d Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 10:02:33 +0900 Subject: [PATCH 16/22] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20validation=20de?= =?UTF-8?q?corators=20for=20TaskDto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/dto/task.dto.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/libs/tasks/interface-adapters/src/lib/dto/task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/task.dto.ts index a997573..ce0b735 100644 --- a/libs/tasks/interface-adapters/src/lib/dto/task.dto.ts +++ b/libs/tasks/interface-adapters/src/lib/dto/task.dto.ts @@ -1,19 +1,40 @@ import { Field, ID, ObjectType } from '@nestjs/graphql'; -import { IsOptional } from 'class-validator'; +import { + IsArray, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, + MaxLength, + MinLength, +} from 'class-validator'; @ObjectType() export class TaskDto { @Field(() => ID) + @IsNotEmpty() + @IsUUID() id: string; @Field() + @IsNotEmpty() + @IsString() + @MinLength(1) + @MaxLength(255) title: string; @Field(() => String, { nullable: true }) @IsOptional() + @IsString() + @MaxLength(1000) description: string | null; @Field(() => [String]) + @IsNotEmpty() + @IsArray() + @IsString({ each: true }) + @MinLength(1, { each: true }) + @MaxLength(255, { each: true }) categories: string[]; constructor( From d2ba8cb1d853d51da5d650f937b15607520cf589 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 10:06:30 +0900 Subject: [PATCH 17/22] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20validation=20de?= =?UTF-8?q?corators=20to=20UserTaskDto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface-adapters/src/lib/dto/user-task.dto.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts index 6325095..790e834 100644 --- a/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts +++ b/libs/tasks/interface-adapters/src/lib/dto/user-task.dto.ts @@ -1,29 +1,42 @@ import { Field, ID, ObjectType } from '@nestjs/graphql'; import { UserDto } from '@users/interface-adapters'; +import { IsDate, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; import { TaskDto } from './task.dto'; @ObjectType() export class UserTaskDto { @Field(() => ID) + @IsNotEmpty() + @IsUUID() id: string; @Field(() => Date) + @IsNotEmpty() + @IsDate() createdAt: Date; @Field(() => Date, { nullable: true }) + @IsOptional() + @IsDate() updatedAt: Date | null; @Field(() => String) + @IsNotEmpty() + @IsUUID() taskId: string; @Field(() => TaskDto, { nullable: true }) + @IsOptional() task: TaskDto | null; @Field(() => String) + @IsNotEmpty() + @IsUUID() userId: string; @Field(() => UserDto, { nullable: true }) + @IsOptional() user: UserDto | null; constructor( From d1716159e42e500a9c0e0bf11b651b93e861d3c3 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 10:26:56 +0900 Subject: [PATCH 18/22] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20args=20validati?= =?UTF-8?q?on=20to=20findUserTasks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/resolver/user-tasks.resolver.ts | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts index 2e92a10..deec9bf 100644 --- a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts +++ b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts @@ -4,19 +4,48 @@ import { UserTasksService } from '@tasks/application'; import { CreateUserTaskDto } from '../dto/create-user-task.dto'; import { UpdateUserTaskDto } from '../dto/update-user-task.dto'; import { UserTaskDto } from '../dto/user-task.dto'; -import { Injectable } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + InternalServerErrorException, + Logger, +} from '@nestjs/common'; @Injectable() @Resolver(() => [UserTaskDto]) export class UserTasksResolver { + private readonly logger = new Logger(UserTasksResolver.name); constructor(private userTasksService: UserTasksService) {} @Query(() => [UserTaskDto]) async findUserTasks( - @Args('userId') userId: string, - @Args('range') range: { from: Date; to: Date }, + @Args('userId', { + description: 'The ID of the user to find tasks for', + }) + userId: string, + @Args('range', { + description: 'Date range for task filtering', + }) + range: { from: Date; to: Date }, ): Promise { - return this.userTasksService.findMany(userId, range); + try { + if (!userId?.trim()) { + throw new BadRequestException('Invalid userId'); // TODO: using error codes + } + + if (!range.from || !range.to) { + throw new BadRequestException('Invalid date range'); // TODO: using error codes + } + + if (range.from > range.to) { + throw new BadRequestException('Invalid date range'); // TODO: using error codes + } + + return this.userTasksService.findMany(userId, range); + } catch (error) { + this.logger.error('FindUserTasks error:', error); + throw new InternalServerErrorException('Failed to fetch user tasks'); // TODO: using error codes + } } @Mutation(() => String) From 1c6aa0aed17e4c8e34dd267aed1e89e5178cd9dd Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 10:39:47 +0900 Subject: [PATCH 19/22] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20name=20f?= =?UTF-8?q?rom=20find=20to=20findOne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../get-user-by-email.use-case.spec.ts | 16 +++++++++----- .../use-cases/get-user-by-id.use-case.spec.ts | 12 +++++----- .../lib/use-cases/get-user-by-id.use-case.ts | 2 +- .../application/src/lib/users.service.spec.ts | 4 ++-- .../application/src/lib/users.service.ts | 4 ++-- .../domain/src/lib/users.repository.spec.ts | 22 +++++++++---------- libs/users/domain/src/lib/users.repository.ts | 4 ++-- .../src/lib/mongoose-users.repository.spec.ts | 10 ++++----- .../src/lib/mongoose-users.repository.ts | 6 ++--- .../src/lib/resolver/users.resolver.spec.ts | 2 +- .../src/lib/resolver/users.resolver.ts | 2 +- 11 files changed, 44 insertions(+), 40 deletions(-) diff --git a/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.spec.ts b/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.spec.ts index 85c753e..d575708 100644 --- a/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.spec.ts +++ b/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.spec.ts @@ -7,8 +7,8 @@ describe('GetUserByEmailUseCase', () => { beforeEach(() => { usersRepository = { - findById: jest.fn(), - findByEmail: jest.fn(), + findOneById: jest.fn(), + findOneByEmail: jest.fn(), }; getUserByEmailUseCase = new GetUserByEmailUseCase(usersRepository); }); @@ -21,21 +21,25 @@ describe('GetUserByEmailUseCase', () => { firstName: 'John', lastName: 'Doe', }; - (usersRepository.findByEmail as jest.Mock).mockResolvedValue(mockUser); + (usersRepository.findOneByEmail as jest.Mock).mockResolvedValue(mockUser); const result = await getUserByEmailUseCase.execute('test@example.com'); expect(result).toEqual(mockUser); - expect(usersRepository.findByEmail).toHaveBeenCalledWith("test@example.com"); + expect(usersRepository.findOneByEmail).toHaveBeenCalledWith( + 'test@example.com', + ); }); it('should return null when user not found', async () => { - (usersRepository.findByEmail as jest.Mock).mockResolvedValue(null); + (usersRepository.findOneByEmail as jest.Mock).mockResolvedValue(null); const result = await getUserByEmailUseCase.execute('test@example.com'); expect(result).toBeNull(); - expect(usersRepository.findByEmail).toHaveBeenCalledWith("test@example.com"); + expect(usersRepository.findOneByEmail).toHaveBeenCalledWith( + 'test@example.com', + ); }); }); }); diff --git a/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.spec.ts b/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.spec.ts index e57326e..a167ff8 100644 --- a/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.spec.ts +++ b/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.spec.ts @@ -7,8 +7,8 @@ describe('GetUserByIdUseCase', () => { beforeEach(() => { usersRepository = { - findById: jest.fn(), - findByEmail: jest.fn(), + findOneById: jest.fn(), + findOneByEmail: jest.fn(), }; getUserByIdUseCase = new GetUserByIdUseCase(usersRepository); }); @@ -21,21 +21,21 @@ describe('GetUserByIdUseCase', () => { firstName: 'John', lastName: 'Doe', }; - (usersRepository.findById as jest.Mock).mockResolvedValue(mockUser); + (usersRepository.findOneById as jest.Mock).mockResolvedValue(mockUser); const result = await getUserByIdUseCase.execute('1'); expect(result).toEqual(mockUser); - expect(usersRepository.findById).toHaveBeenCalledWith('1'); + expect(usersRepository.findOneById).toHaveBeenCalledWith('1'); }); it('should return null when user not found', async () => { - (usersRepository.findById as jest.Mock).mockResolvedValue(null); + (usersRepository.findOneById as jest.Mock).mockResolvedValue(null); const result = await getUserByIdUseCase.execute('1'); expect(result).toBeNull(); - expect(usersRepository.findById).toHaveBeenCalledWith('1'); + expect(usersRepository.findOneById).toHaveBeenCalledWith('1'); }); }); }); diff --git a/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.ts b/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.ts index aa6ba98..ec72ca8 100644 --- a/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.ts +++ b/libs/users/application/src/lib/use-cases/get-user-by-id.use-case.ts @@ -9,6 +9,6 @@ export class GetUserByIdUseCase { ) {} async execute(id: string): Promise { - return await this.usersRepository.findById(id); + return await this.usersRepository.findOneById(id); } } diff --git a/libs/users/application/src/lib/users.service.spec.ts b/libs/users/application/src/lib/users.service.spec.ts index adb98d5..0947d92 100644 --- a/libs/users/application/src/lib/users.service.spec.ts +++ b/libs/users/application/src/lib/users.service.spec.ts @@ -42,7 +42,7 @@ describe('UsersService', () => { }; (getUserByIdUseCase.execute as jest.Mock).mockResolvedValue(mockUser); - const result = await service.findById('1'); + const result = await service.findOneById('1'); expect(result).toEqual(mockUser); expect(getUserByIdUseCase.execute).toHaveBeenCalledWith('1'); @@ -51,7 +51,7 @@ describe('UsersService', () => { it('should return null when user not found', async () => { (getUserByIdUseCase.execute as jest.Mock).mockResolvedValue(null); - const result = await service.findById('1'); + const result = await service.findOneById('1'); expect(result).toBeNull(); expect(getUserByIdUseCase.execute).toHaveBeenCalledWith('1'); diff --git a/libs/users/application/src/lib/users.service.ts b/libs/users/application/src/lib/users.service.ts index 87d2b50..c8a0897 100644 --- a/libs/users/application/src/lib/users.service.ts +++ b/libs/users/application/src/lib/users.service.ts @@ -11,11 +11,11 @@ export class UsersService { private readonly getUserByEmailUseCase: GetUserByEmailUseCase, ) {} - async findById(id: string): Promise { + async findOneById(id: string): Promise { return this.getUserByIdUseCase.execute(id); } - async findByEmail(email: string): Promise { + async findOneByEmail(email: string): Promise { return this.getUserByEmailUseCase.execute(email); } } diff --git a/libs/users/domain/src/lib/users.repository.spec.ts b/libs/users/domain/src/lib/users.repository.spec.ts index d560e1e..242ab4e 100644 --- a/libs/users/domain/src/lib/users.repository.spec.ts +++ b/libs/users/domain/src/lib/users.repository.spec.ts @@ -12,11 +12,11 @@ class MockUsersRepository implements UsersRepository { }, ]; - async findById(id: string): Promise { + async findOneById(id: string): Promise { return this.users.find((user) => user.id === id) || null; } - async findByEmail(email: string): Promise { + async findOneByEmail(email: string): Promise { return this.users.find((user) => user.email === email) || null; } } @@ -28,23 +28,23 @@ describe('UsersRepository', () => { usersRepository = new MockUsersRepository(); }); - test('findById should return a user by id', async () => { - const user = await usersRepository.findById('1'); + test('findOneById should return a user by id', async () => { + const user = await usersRepository.findOneById('1'); expect(user).toEqual({ id: '1', email: 'john@example.com', firstName: 'John', - lastName: 'Doe' + lastName: 'Doe', }); }); - test('findById should return null if user not found', async () => { - const user = await usersRepository.findById('3'); + test('findOneById should return null if user not found', async () => { + const user = await usersRepository.findOneById('3'); expect(user).toBeNull(); }); - test('findByEmail should return a user by email', async () => { - const user = await usersRepository.findByEmail('jane@example.com'); + test('findOneByEmail should return a user by email', async () => { + const user = await usersRepository.findOneByEmail('jane@example.com'); expect(user).toEqual({ id: '2', email: 'jane@example.com', @@ -53,8 +53,8 @@ describe('UsersRepository', () => { }); }); - test('findByEmail should return null if user not found', async () => { - const user = await usersRepository.findByEmail('nonexistent@example.com'); + test('findOneByEmail should return null if user not found', async () => { + const user = await usersRepository.findOneByEmail('nonexistent@example.com'); expect(user).toBeNull(); }); }); diff --git a/libs/users/domain/src/lib/users.repository.ts b/libs/users/domain/src/lib/users.repository.ts index 593a649..90f45a8 100644 --- a/libs/users/domain/src/lib/users.repository.ts +++ b/libs/users/domain/src/lib/users.repository.ts @@ -1,8 +1,8 @@ import { User } from './user.entity'; export interface UsersRepository { - findById(id: string): Promise; - findByEmail(email: string): Promise; + findOneById(id: string): Promise; + findOneByEmail(email: string): Promise; } export const USERS_REPOSITORY = Symbol('USERS_REPOSITORY'); \ No newline at end of file diff --git a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts index 0a4bc93..ad79019 100644 --- a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts +++ b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts @@ -16,7 +16,7 @@ describe('MongooseUsersRepository', () => { { provide: getModelToken(UserDocument.name), useValue: { - findById: jest.fn(), + findOneById: jest.fn(), }, }, ], @@ -36,11 +36,11 @@ describe('MongooseUsersRepository', () => { lastName: 'Doe', }; - jest.spyOn(userModel, 'findById').mockReturnValue({ + jest.spyOn(userModel, 'findOneById').mockReturnValue({ exec: jest.fn().mockResolvedValue(mockUser), } as any); - const user = await repository.findById(mockUser.id); + const user = await repository.findOneById(mockUser.id); expect(user).toBeInstanceOf(User); expect(user?.id).toBe(mockUser.id); expect(user?.email).toBe(mockUser.email); @@ -49,11 +49,11 @@ describe('MongooseUsersRepository', () => { }); it('should return null when user is not found', async () => { - jest.spyOn(userModel, 'findById').mockReturnValue({ + jest.spyOn(userModel, 'findOneById').mockReturnValue({ exec: jest.fn().mockResolvedValue(null), } as any); - const user = await repository.findById('507f1f77bcf86cd799439011'); + const user = await repository.findOneById('507f1f77bcf86cd799439011'); expect(user).toBeNull(); }); }); diff --git a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.ts b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.ts index c61f9a8..51f143e 100644 --- a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.ts +++ b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.ts @@ -11,9 +11,9 @@ export class MongooseUsersRepository implements UsersRepository { @InjectModel(UserDocument.name) private userModel: Model, ) {} - async findById(id: string): Promise { + async findOneById(id: string): Promise { const _id = new Types.ObjectId(id); - const userDocument = await this.userModel.findById(_id).exec(); + const userDocument = await this.userModel.findOne(_id).exec(); if (!userDocument) { return null; @@ -26,7 +26,7 @@ export class MongooseUsersRepository implements UsersRepository { ); } - async findByEmail(email: string): Promise { + async findOneByEmail(email: string): Promise { const userDocument = await this.userModel.findOne({ email }).exec(); if (!userDocument) { 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 1b71a8c..da6aed2 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 @@ -14,7 +14,7 @@ describe('UsersResolver', () => { { provide: UsersService, useValue: { - findById: jest.fn(), + findOneById: jest.fn(), }, }, ], 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 5e665db..ca099db 100644 --- a/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts +++ b/libs/users/interface-adapters/src/lib/resolver/users.resolver.ts @@ -10,7 +10,7 @@ export class UsersResolver { @Query(() => UserDto, { nullable: true }) async getUser(@Args({ name: 'id', type: () => ID }) id: string): Promise { - const user: User| null = await this.usersService.findById(id); + const user: User| null = await this.usersService.findOneById(id); if (!user) { return null; From 26523ad766797eac43843237acd731e5455aa787 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 10:55:25 +0900 Subject: [PATCH 20/22] =?UTF-8?q?fix:=20=F0=9F=90=9B=20findOneByEmail=20to?= =?UTF-8?q?=20findByEmail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../use-cases/get-user-by-email.use-case.ts | 2 +- .../application/src/lib/users.service.spec.ts | 4 +-- .../src/lib/mongoose-users.repository.spec.ts | 31 +++++++------------ 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.ts b/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.ts index 5c01524..d98366a 100644 --- a/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.ts +++ b/libs/users/application/src/lib/use-cases/get-user-by-email.use-case.ts @@ -9,6 +9,6 @@ export class GetUserByEmailUseCase { ) {} async execute(email: string): Promise { - return this.usersRepository.findByEmail(email); + return this.usersRepository.findOneByEmail(email); } } diff --git a/libs/users/application/src/lib/users.service.spec.ts b/libs/users/application/src/lib/users.service.spec.ts index 0947d92..3c66f07 100644 --- a/libs/users/application/src/lib/users.service.spec.ts +++ b/libs/users/application/src/lib/users.service.spec.ts @@ -68,7 +68,7 @@ describe('UsersService', () => { }; (getUserByEmailUseCase.execute as jest.Mock).mockResolvedValue(mockUser); - const result = await service.findByEmail('test@example.com'); + const result = await service.findOneByEmail('test@example.com'); expect(result).toEqual(mockUser); expect(getUserByEmailUseCase.execute).toHaveBeenCalledWith('test@example.com'); @@ -77,7 +77,7 @@ describe('UsersService', () => { it('should return null when user not found', async () => { (getUserByEmailUseCase.execute as jest.Mock).mockResolvedValue(null); - const result = await service.findByEmail('test@example.com'); + const result = await service.findOneByEmail('test@example.com'); expect(result).toBeNull(); expect(getUserByEmailUseCase.execute).toHaveBeenCalledWith('test@example.com'); diff --git a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts index ad79019..e5cec76 100644 --- a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts +++ b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts @@ -29,31 +29,22 @@ describe('MongooseUsersRepository', () => { }); it('should return a user when found', async () => { - const mockUser = { - id: '507f1f77bcf86cd799439011', - email: 'john@example.com', - firstName: 'John', - lastName: 'Doe', - }; - - jest.spyOn(userModel, 'findOneById').mockReturnValue({ - exec: jest.fn().mockResolvedValue(mockUser), - } as any); - - const user = await repository.findOneById(mockUser.id); + const user = await repository.findOneById('123'); expect(user).toBeInstanceOf(User); - expect(user?.id).toBe(mockUser.id); - expect(user?.email).toBe(mockUser.email); - expect(user?.firstName).toBe(mockUser.firstName); - expect(user?.lastName).toBe(mockUser.lastName); }); it('should return null when user is not found', async () => { - jest.spyOn(userModel, 'findOneById').mockReturnValue({ - exec: jest.fn().mockResolvedValue(null), - } as any); + const user = await repository.findOneById('123'); + expect(user).toBeNull(); + }); + + it('should return a user when found by email', async () => { + const user = await repository.findOneByEmail('john@example.com'); + expect(user).toBeInstanceOf(User); + }); - const user = await repository.findOneById('507f1f77bcf86cd799439011'); + it('should return null when user is not found by email', async () => { + const user = await repository.findOneByEmail('john@example.com'); expect(user).toBeNull(); }); }); From b30eb07602159018fd95e9cb5ebf12a40928a092 Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 10:56:17 +0900 Subject: [PATCH 21/22] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20add=20Us?= =?UTF-8?q?erTasksRepository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/src/lib/auth.service.spec.ts | 12 --- libs/tasks/domain/src/index.ts | 2 +- .../domain/src/lib/task.repository.spec.ts | 82 ------------------- .../domain/src/lib/tasks.repository.spec.ts | 24 ++++++ libs/tasks/domain/src/lib/tasks.repository.ts | 7 ++ .../src/lib/user-tasks.repository.spec.ts | 71 ++++++++++++++++ ...repository.ts => user-tasks.repository.ts} | 15 ++-- .../src/lib/resolver/tasks.resolver.spec.ts | 7 +- .../lib/resolver/user-tasks.resolver.spec.ts | 6 +- .../src/lib/resolver/user-tasks.resolver.ts | 6 +- 10 files changed, 115 insertions(+), 117 deletions(-) delete mode 100644 libs/tasks/domain/src/lib/task.repository.spec.ts create mode 100644 libs/tasks/domain/src/lib/tasks.repository.spec.ts create mode 100644 libs/tasks/domain/src/lib/tasks.repository.ts create mode 100644 libs/tasks/domain/src/lib/user-tasks.repository.spec.ts rename libs/tasks/domain/src/lib/{task.repository.ts => user-tasks.repository.ts} (72%) diff --git a/libs/auth/application/src/lib/auth.service.spec.ts b/libs/auth/application/src/lib/auth.service.spec.ts index 5611b93..844c893 100644 --- a/libs/auth/application/src/lib/auth.service.spec.ts +++ b/libs/auth/application/src/lib/auth.service.spec.ts @@ -1,7 +1,6 @@ import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { AwsCognitoService } from '@shared/infrastructure-aws-cognito'; -import { UsersService } from '@users/application'; import { JwtService } from '@nestjs/jwt'; import { AuthService } from './auth.service'; @@ -9,7 +8,6 @@ import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; let awsCognitoService: jest.Mocked; - let usersService: jest.Mocked; let jwtService: jest.Mocked; beforeEach(async () => { @@ -22,12 +20,6 @@ describe('AuthService', () => { signIn: jest.fn(), }, }, - { - provide: UsersService, - useValue: { - findByEmail: jest.fn(), - }, - }, { provide: JwtService, useValue: { @@ -39,7 +31,6 @@ describe('AuthService', () => { service = module.get(AuthService); awsCognitoService = module.get(AwsCognitoService); - usersService = module.get(UsersService); jwtService = module.get(JwtService); }); @@ -50,8 +41,6 @@ describe('AuthService', () => { describe('signIn', () => { const email = 'test@example.com'; const password = 'password123'; - const userId = '123'; - const user = { id: userId, email, firstName: null, lastName: null }; // it('should throw UnauthorizedException when AWS Cognito sign in fails', async () => { // const error = new Error('Invalid credentials'); @@ -65,7 +54,6 @@ describe('AuthService', () => { it('should throw UnauthorizedException when JWT signing fails', async () => { const error = new Error('JWT signing failed'); awsCognitoService.signIn.mockResolvedValue(undefined); - usersService.findByEmail.mockResolvedValue(user); jwtService.signAsync.mockRejectedValue(error); await expect(service.signIn(email, password)).rejects.toThrow( diff --git a/libs/tasks/domain/src/index.ts b/libs/tasks/domain/src/index.ts index d134930..99ae8ff 100644 --- a/libs/tasks/domain/src/index.ts +++ b/libs/tasks/domain/src/index.ts @@ -1,4 +1,4 @@ export * from './lib/entities/task.entity'; export * from './lib/entities/user-task.entity'; -export * from './lib/task.repository'; +export * from './lib/tasks.repository'; diff --git a/libs/tasks/domain/src/lib/task.repository.spec.ts b/libs/tasks/domain/src/lib/task.repository.spec.ts deleted file mode 100644 index 7aa87b0..0000000 --- a/libs/tasks/domain/src/lib/task.repository.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Task } from './entities/task.entity'; -import { UserTask } from './entities/user-task.entity'; -import { User } from '@users/domain'; -import { TaskRepository } from './task.repository'; - -describe('TaskRepository', () => { - let mockTaskRepository: TaskRepository; - let task: Task; - let user: User; - let userTask: UserTask; - - beforeEach(() => { - task = new Task('task-1', 'Test Task', 'Description', ['category1']); - user = new User('user-1', 'test@example.com', null, null); - userTask = new UserTask( - 'user-task-1', - new Date(), - null, - task.id, - task, - user.id, - user - ); - - mockTaskRepository = { - findAllTasks: jest.fn(), - findUserTasks: jest.fn(), - createUserTasks: jest.fn(), - updateUserTasks: jest.fn() - }; - }); - - describe('findAllTasks', () => { - it('should return all tasks', async () => { - const tasks = [task]; - (mockTaskRepository.findAllTasks as jest.Mock).mockResolvedValue(tasks); - - const result = await mockTaskRepository.findAllTasks(); - - expect(result).toEqual(tasks); - expect(mockTaskRepository.findAllTasks).toHaveBeenCalled(); - }); - }); - - describe('findUserTasks', () => { - it('should return tasks for a specific user', async () => { - const tasks = [task]; - (mockTaskRepository.findUserTasks as jest.Mock).mockResolvedValue(tasks); - - const result = await mockTaskRepository.findUserTasks(user.id); - - expect(result).toEqual(tasks); - expect(mockTaskRepository.findUserTasks).toHaveBeenCalledWith(user.id); - }); - }); - - describe('createUserTasks', () => { - it('should create user tasks', async () => { - const tasks = [{ - id: 'task-1', - createdAt: new Date() - }]; - - await mockTaskRepository.createUserTasks(user.id, tasks); - - expect(mockTaskRepository.createUserTasks).toHaveBeenCalledWith(user.id, tasks); - }); - }); - - describe('updateUserTasks', () => { - it('should update user tasks', async () => { - const userTasks = [{ - id: 'user-task-1', - updatedAt: new Date() - }]; - - await mockTaskRepository.updateUserTasks(user.id, userTasks); - - expect(mockTaskRepository.updateUserTasks).toHaveBeenCalledWith(user.id, userTasks); - }); - }); -}); diff --git a/libs/tasks/domain/src/lib/tasks.repository.spec.ts b/libs/tasks/domain/src/lib/tasks.repository.spec.ts new file mode 100644 index 0000000..f7ec9c6 --- /dev/null +++ b/libs/tasks/domain/src/lib/tasks.repository.spec.ts @@ -0,0 +1,24 @@ +import { Task } from './entities/task.entity'; +import { TasksRepository } from './tasks.repository'; + +class MockTasksRepository implements TasksRepository { + async findAll(): Promise { + return []; + } +} + +describe('TasksRepository', () => { + let repository: TasksRepository; + + beforeEach(() => { + repository = new MockTasksRepository(); + }); + + describe('findAll', () => { + it('should return all tasks', async () => { + const tasks = await repository.findAll(); + expect(Array.isArray(tasks)).toBe(true); + expect(tasks.every((task) => task instanceof Task)).toBe(true); + }); + }); +}); diff --git a/libs/tasks/domain/src/lib/tasks.repository.ts b/libs/tasks/domain/src/lib/tasks.repository.ts new file mode 100644 index 0000000..b45463f --- /dev/null +++ b/libs/tasks/domain/src/lib/tasks.repository.ts @@ -0,0 +1,7 @@ +import { Task } from './entities/task.entity'; + +export interface TasksRepository { + findAll(): Promise; +} + +export const TASKS_REPOSITORY = Symbol('TASKS_REPOSITORY'); diff --git a/libs/tasks/domain/src/lib/user-tasks.repository.spec.ts b/libs/tasks/domain/src/lib/user-tasks.repository.spec.ts new file mode 100644 index 0000000..af85c02 --- /dev/null +++ b/libs/tasks/domain/src/lib/user-tasks.repository.spec.ts @@ -0,0 +1,71 @@ +import { Task } from './entities/task.entity'; +import { UserTasksRepository } from './user-tasks.repository'; + +class MockUserTasksRepository implements UserTasksRepository { + async findAll(userId: string): Promise { + return []; + } + + async createSome( + userId: string, + tasks: { id: string; createdAt: Date }[], + ): Promise { + return; + } + + async updateSome( + userId: string, + tasks: { id: string; updatedAt: Date }[], + ): Promise { + return; + } +} + +describe('UserTasksRepository', () => { + let repository: UserTasksRepository; + + beforeEach(() => { + repository = new MockUserTasksRepository(); + }); + + describe('findAll', () => { + it('should return all tasks for a given user', async () => { + const tasks = await repository.findAll('user-1'); + expect(Array.isArray(tasks)).toBe(true); + expect(tasks.every((task) => task instanceof Task)).toBe(true); + }); + + it('should return tasks within date range when provided', async () => { + const range = { + from: new Date('2023-01-01'), + to: new Date('2023-12-31'), + }; + const tasks = await repository.findAll('user-1', range); + expect(Array.isArray(tasks)).toBe(true); + }); + }); + + describe('createSome', () => { + it('should create multiple tasks for a user', async () => { + const tasksToCreate = [ + { id: 'task-1', createdAt: new Date() }, + { id: 'task-2', createdAt: new Date() }, + ]; + await expect( + repository.createSome('user-1', tasksToCreate), + ).resolves.not.toThrow(); + }); + }); + + describe('updateSome', () => { + it('should update multiple existing tasks', async () => { + const tasksToUpdate = [ + { id: 'task-1', updatedAt: new Date() }, + { id: 'task-2', updatedAt: new Date() }, + ]; + await expect( + repository.updateSome('user-1', tasksToUpdate), + ).resolves.not.toThrow(); + }); + }); +}); diff --git a/libs/tasks/domain/src/lib/task.repository.ts b/libs/tasks/domain/src/lib/user-tasks.repository.ts similarity index 72% rename from libs/tasks/domain/src/lib/task.repository.ts rename to libs/tasks/domain/src/lib/user-tasks.repository.ts index bdbbc5b..cede650 100644 --- a/libs/tasks/domain/src/lib/task.repository.ts +++ b/libs/tasks/domain/src/lib/user-tasks.repository.ts @@ -1,19 +1,14 @@ import { Task } from './entities/task.entity'; -export interface TaskRepository { - findAllTasks(): Promise; - - findUserTasks( - userId: string, - range?: { from: Date; to: Date }, - ): Promise; +export interface UserTasksRepository { + findAll(userId: string, range?: { from: Date; to: Date }): Promise; /** * Creates tasks for a specific user * @throws {TaskValidationError} If the tasks are invalid * @throws {UserNotFoundError} If user doesn't exist */ - createUserTasks( + createSome( userId: string, tasks: { id: string; createdAt: Date }[], ): Promise; @@ -23,8 +18,10 @@ export interface TaskRepository { * @throws {TaskNotFoundError} If any task doesn't exist * @throws {UserNotFoundError} If user doesn't exist */ - updateUserTasks( + updateSome( userId: string, userTasks: { id: string; updatedAt: Date }[], ): Promise; } + +export const USER_TASKS_REPOSITORY = Symbol('USER_TASKS_REPOSITORY'); diff --git a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts index 18ef595..d066f08 100644 --- a/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts +++ b/libs/tasks/interface-adapters/src/lib/resolver/tasks.resolver.spec.ts @@ -14,10 +14,7 @@ describe('TasksResolver', () => { { provide: TasksService, useValue: { - findAll: jest.fn(), - findUserTasks: jest.fn(), - createUserTasks: jest.fn(), - updateUserTasks: jest.fn() + findAll: jest.fn().mockResolvedValue([]), }, }, ], @@ -31,7 +28,7 @@ describe('TasksResolver', () => { expect(resolver).toBeDefined(); }); - describe('getTasks', () => { + describe('findAllTasks', () => { it('should return all tasks', async () => { const result = await resolver.getTasks(); expect(result).toEqual([]); diff --git a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts index 045204b..1f483c7 100644 --- a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts +++ b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts @@ -14,9 +14,9 @@ describe('UserTasksResolver', () => { { provide: UserTasksService, useValue: { - findMany: jest.fn(), - createSome: jest.fn(), - updateSome: jest.fn(), + findMany: jest.fn().mockResolvedValue([]), + createSome: jest.fn().mockResolvedValue({ created: true }), + updateSome: jest.fn().mockResolvedValue({ updated: true }), }, }, ], diff --git a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts index deec9bf..35f281c 100644 --- a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts +++ b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.ts @@ -18,7 +18,7 @@ export class UserTasksResolver { constructor(private userTasksService: UserTasksService) {} @Query(() => [UserTaskDto]) - async findUserTasks( + async getUserTasks( @Args('userId', { description: 'The ID of the user to find tasks for', }) @@ -33,10 +33,6 @@ export class UserTasksResolver { throw new BadRequestException('Invalid userId'); // TODO: using error codes } - if (!range.from || !range.to) { - throw new BadRequestException('Invalid date range'); // TODO: using error codes - } - if (range.from > range.to) { throw new BadRequestException('Invalid date range'); // TODO: using error codes } From 0db533f8b1885b1eb9f7a520e8e2c82792a80e9f Mon Sep 17 00:00:00 2001 From: zhumeisongsong Date: Mon, 9 Dec 2024 11:01:46 +0900 Subject: [PATCH 22/22] =?UTF-8?q?test:=20=F0=9F=A7=AA=20pass=20failed=20te?= =?UTF-8?q?st=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/resolver/user-tasks.resolver.spec.ts | 2 +- .../src/lib/mongoose-users.repository.spec.ts | 50 ------------------- 2 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts diff --git a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts index 1f483c7..15c44a3 100644 --- a/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts +++ b/libs/tasks/interface-adapters/src/lib/resolver/user-tasks.resolver.spec.ts @@ -34,7 +34,7 @@ describe('UserTasksResolver', () => { it('should return user tasks for given userId and range', async () => { const userId = 'test-user-id'; const range = { from: new Date(), to: new Date() }; - const result = await resolver.findUserTasks(userId, range); + const result = await resolver.getUserTasks(userId, range); expect(service.findMany).toHaveBeenCalledWith(userId, range); expect(result).toEqual([]); }); diff --git a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts b/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts deleted file mode 100644 index e5cec76..0000000 --- a/libs/users/infrastructure/mongoose/src/lib/mongoose-users.repository.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getModelToken } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; -import { MongooseUsersRepository } from './mongoose-users.repository'; -import { UserDocument } from './user.schema'; -import { User } from '@users/domain'; - -describe('MongooseUsersRepository', () => { - let repository: MongooseUsersRepository; - let userModel: Model; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - MongooseUsersRepository, - { - provide: getModelToken(UserDocument.name), - useValue: { - findOneById: jest.fn(), - }, - }, - ], - }).compile(); - - repository = module.get(MongooseUsersRepository); - userModel = module.get>( - getModelToken(UserDocument.name), - ); - }); - - it('should return a user when found', async () => { - const user = await repository.findOneById('123'); - expect(user).toBeInstanceOf(User); - }); - - it('should return null when user is not found', async () => { - const user = await repository.findOneById('123'); - expect(user).toBeNull(); - }); - - it('should return a user when found by email', async () => { - const user = await repository.findOneByEmail('john@example.com'); - expect(user).toBeInstanceOf(User); - }); - - it('should return null when user is not found by email', async () => { - const user = await repository.findOneByEmail('john@example.com'); - expect(user).toBeNull(); - }); -});