From b29cecd12598f190ba750aacf84f8bc50f02af60 Mon Sep 17 00:00:00 2001 From: jbuget Date: Wed, 9 Feb 2022 15:59:36 +0100 Subject: [PATCH] :sparkles: feat: add DELETE /tasks/:id route --- src/domain/entities/Task.ts | 4 +-- src/domain/entities/TaskRepository.ts | 4 ++- src/domain/usecases/commands/create_task.ts | 2 +- src/domain/usecases/commands/delete_task.ts | 13 +++++++ src/infrastructure/app.ts | 10 ++++-- .../repositories/TaskRepositorySql.ts | 12 +++++++ test/domain/models/Task.test.ts | 8 ++--- .../usecases/commands/create_task.test.ts | 3 ++ .../usecases/queries/list_tasks.test.ts | 3 ++ test/infrastructure/app.test.ts | 34 ++++++++++++++++--- .../repositories/TaskRepositorySql.test.ts | 28 +++++++++++++-- test/test-helpers.ts | 5 +++ 12 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 src/domain/usecases/commands/delete_task.ts diff --git a/src/domain/entities/Task.ts b/src/domain/entities/Task.ts index be7d820..bf31d38 100644 --- a/src/domain/entities/Task.ts +++ b/src/domain/entities/Task.ts @@ -6,13 +6,13 @@ export enum Status { } export class Task { - id: number | null; + id: number | undefined; createdAt: Date; updatedAt: Date; content: string; status: Status; - constructor(options : {id: number | null, content: string, createdAt: Date, status?: Status, updatedAt?: Date}) { + constructor(options : {id: number | undefined, content: string, createdAt: Date, status?: Status, updatedAt?: Date}) { this.id = options.id; this.createdAt = options.createdAt; this.updatedAt = options.updatedAt || this.createdAt; diff --git a/src/domain/entities/TaskRepository.ts b/src/domain/entities/TaskRepository.ts index c6da6e7..5b05ee3 100644 --- a/src/domain/entities/TaskRepository.ts +++ b/src/domain/entities/TaskRepository.ts @@ -6,5 +6,7 @@ export interface TaskRepository { save(task: Task): Promise; - findById(id: number): Promise; + findById(id: number): Promise; + + delete(task: Task): Promise; } diff --git a/src/domain/usecases/commands/create_task.ts b/src/domain/usecases/commands/create_task.ts index 502336a..fb2abe0 100644 --- a/src/domain/usecases/commands/create_task.ts +++ b/src/domain/usecases/commands/create_task.ts @@ -4,7 +4,7 @@ import { Status, Task } from '../../entities/Task'; async function createTask(params: any, taskRepository: TaskRepository): Promise { const now = new Date(); const task = new Task({ - id: null, + id: undefined, content: params.content, createdAt: now, updatedAt: now, diff --git a/src/domain/usecases/commands/delete_task.ts b/src/domain/usecases/commands/delete_task.ts new file mode 100644 index 0000000..761f6c8 --- /dev/null +++ b/src/domain/usecases/commands/delete_task.ts @@ -0,0 +1,13 @@ +import { TaskRepository } from '../../entities/TaskRepository'; +import { Task } from '../../entities/Task'; + +async function deleteTask(id: number, taskRepository: TaskRepository): Promise { + const task: Task | null = await taskRepository.findById(id); + if (task) { + await taskRepository.delete(task); + } +} + +export { + deleteTask +}; diff --git a/src/infrastructure/app.ts b/src/infrastructure/app.ts index 99155ef..8d9c1d4 100644 --- a/src/infrastructure/app.ts +++ b/src/infrastructure/app.ts @@ -4,6 +4,7 @@ import { createTask } from '../domain/usecases/commands/create_task'; import { TaskRepository } from '../domain/entities/TaskRepository'; import { container } from './container'; import { getTaskById } from '../domain/usecases/queries/get_task_by_id'; +import { deleteTask } from '../domain/usecases/commands/delete_task'; function build(): FastifyInstance { const logger = container.resolve('logger'); @@ -31,7 +32,7 @@ function build(): FastifyInstance { // Get a task server.get('/tasks/:id', async function (request: FastifyRequest, reply: FastifyReply) { - const taskId = parseInt(request.params.id); + const taskId: number = parseInt(request.params.id); if (isNaN(taskId)) { reply.code(429).send(); } else { @@ -61,8 +62,11 @@ function build(): FastifyInstance { }); // Delete a task - server.delete('/tasks/:id', async function (request, reply) { - reply.code(501).send(); + server.delete('/tasks/:id', async function (request: FastifyRequest, reply: FastifyReply) { + const taskId: number = parseInt(request.params.id); + const taskRepository: TaskRepository = container.resolve('taskRepository'); + await deleteTask(taskId, taskRepository); + reply.code(204).send(); }); return server; diff --git a/src/infrastructure/repositories/TaskRepositorySql.ts b/src/infrastructure/repositories/TaskRepositorySql.ts index f7d9915..e7305e2 100644 --- a/src/infrastructure/repositories/TaskRepositorySql.ts +++ b/src/infrastructure/repositories/TaskRepositorySql.ts @@ -73,4 +73,16 @@ export class TaskRepositorySql implements TaskRepository { } return null; } + + async delete(task: Task): Promise { + const prisma = this.prisma; + try { + const prismaTask = await prisma.task.findUnique({ where: { id: task.id } }); + if (prismaTask) { + await prisma.task.delete({ where: { id: task.id } }); + } + } finally { + await prisma.$disconnect(); + } + } } diff --git a/test/domain/models/Task.test.ts b/test/domain/models/Task.test.ts index 2b2e44f..0c39cd9 100644 --- a/test/domain/models/Task.test.ts +++ b/test/domain/models/Task.test.ts @@ -31,7 +31,7 @@ describe('domain.models.Task', function () { // given const date = new Date('2021-11-15T20:16:00'); const options = { - id: null, + id: undefined, createdAt: date, updatedAt: date, content: 'Dire à ma maman que je l’aime', @@ -42,14 +42,14 @@ describe('domain.models.Task', function () { const task: Task = new Task(options); // then - expect(task.id).toBeNull(); + expect(task.id).toBeUndefined(); }); it('should set status "TO_DO" by default', () => { // given const date = new Date('2021-11-15T20:16:00'); const options = { - id: null, + id: undefined, createdAt: date, updatedAt: date, content: 'Dire à ma maman que je l’aime' @@ -65,7 +65,7 @@ describe('domain.models.Task', function () { it('should set "updatedAd" as "createdAt" by default', () => { const date = new Date('2021-11-15T20:16:00'); const options = { - id: null, + id: undefined, createdAt: date, content: 'Dire à ma maman que je l’aime' }; diff --git a/test/domain/usecases/commands/create_task.test.ts b/test/domain/usecases/commands/create_task.test.ts index bec0ee4..b480165 100644 --- a/test/domain/usecases/commands/create_task.test.ts +++ b/test/domain/usecases/commands/create_task.test.ts @@ -11,6 +11,9 @@ describe('domain.usecases.commands.create_task', function () { const stubbedSavedTask: Task = new Task({ id: 1, createdAt: now, content: params.content }); const taskRepository: TaskRepository = { + delete(/* task: Task */): Promise { + return Promise.resolve(); + }, async findById(id: number): Promise { return null; }, diff --git a/test/domain/usecases/queries/list_tasks.test.ts b/test/domain/usecases/queries/list_tasks.test.ts index d7169c1..6323e40 100644 --- a/test/domain/usecases/queries/list_tasks.test.ts +++ b/test/domain/usecases/queries/list_tasks.test.ts @@ -12,6 +12,9 @@ describe('domain.usecases.queries.list_tasks', function () { const taskList: TaskList = new TaskList([task1, task2]); const taskRepository: TaskRepository = { + delete(/* task: Task */): Promise { + return Promise.resolve(); + }, async findById(id: number): Promise { return null; }, diff --git a/test/infrastructure/app.test.ts b/test/infrastructure/app.test.ts index 5d4b413..d3aa722 100644 --- a/test/infrastructure/app.test.ts +++ b/test/infrastructure/app.test.ts @@ -1,5 +1,5 @@ import { build as app } from '../../src/infrastructure/app'; -import { getPrismaClient, resetDatabase } from '../test-helpers'; +import { getPrismaClient, getRandomInt, resetDatabase } from '../test-helpers'; import { Status } from '../../src/domain/entities/Task'; describe('API', () => { @@ -96,10 +96,11 @@ describe('API', () => { describe('GET /:id', () => { it('should return 200 with the task matching the given ID', async () => { // given + const taskId = getRandomInt(); const prisma = getPrismaClient(); await prisma.task.create({ data: { - id: 1, + id: taskId, content: 'Task 1 content', status: 'DONE', createdAt: new Date('2021-11-09T17:08:02.865Z'), @@ -108,14 +109,14 @@ describe('API', () => { }); // when - const response = await server.inject({ method: 'GET', url: '/tasks/1' }); + const response = await server.inject({ method: 'GET', url: `/tasks/${taskId}` }); // then expect(response.statusCode).toBe(200); expect(response.json()).toStrictEqual({ content: 'Task 1 content', createdAt: '2021-11-09T17:08:02.865Z', - id: 1, + id: taskId, status: 'DONE', updatedAt: '2021-11-09T17:08:02.866Z' }); @@ -139,5 +140,30 @@ describe('API', () => { expect(response.statusCode).toBe(429); }); }); + + describe('DELETE /:id', () => { + it('should delete the given task and return 204', async () => { + // given + const taskId = getRandomInt(); + const prisma = getPrismaClient(); + await prisma.task.create({ + data: { + id: taskId, + content: 'Task 6 content', + status: 'TO_DO', + createdAt: new Date('2022-02-15T17:08:02.865Z'), + updatedAt: new Date('2022-02-15T17:08:02.866Z') + } + }); + + // when + const response = await server.inject({ method: 'DELETE', url: `/tasks/${taskId}` }); + + // then + expect(response.statusCode).toBe(204); + const data = await prisma.task.findUnique({ where: { id: taskId } }); + expect(data).toBeNull(); + }); + }); }); }); diff --git a/test/infrastructure/repositories/TaskRepositorySql.test.ts b/test/infrastructure/repositories/TaskRepositorySql.test.ts index 1a1cc85..504843d 100644 --- a/test/infrastructure/repositories/TaskRepositorySql.test.ts +++ b/test/infrastructure/repositories/TaskRepositorySql.test.ts @@ -72,7 +72,7 @@ describe('infrastructure.repositories.TaskRepositorySql', function () { const prismaTasksBefore = await prisma.task.count(); const taskRepository = new TaskRepositorySql(prisma); - const taskToInsert: Task = new Task({ id: null, content: 'New task', createdAt: new Date(Date.now()) }); + const taskToInsert: Task = new Task({ id: undefined, content: 'New task', createdAt: new Date(Date.now()) }); // when await taskRepository.save(taskToInsert); @@ -88,7 +88,7 @@ describe('infrastructure.repositories.TaskRepositorySql', function () { const taskRepository = new TaskRepositorySql(prisma); const now = new Date(Date.now()); const taskToInsert: Task = new Task({ - id: null, + id: undefined, content: 'New task', status: Status.DONE, createdAt: now, @@ -106,4 +106,28 @@ describe('infrastructure.repositories.TaskRepositorySql', function () { expect(savedTask.updatedAt).toStrictEqual(now); }); }); + + describe('#delete', () => { + it('should ', async () => { + // given + const prisma = getPrismaClient(); + const taskRepository = new TaskRepositorySql(prisma); + const now = new Date(Date.now()); + const taskToInsert: Task = new Task({ + id: undefined, + content: 'New task', + status: Status.DONE, + createdAt: now, + updatedAt: now + }); + const savedTask: Task = await taskRepository.save(taskToInsert); + + // when + await taskRepository.delete(savedTask); + + // then + const data = await prisma.task.findUnique({ where: { id: savedTask.id } }); + expect(data).toBeNull(); + }); + }); }); diff --git a/test/test-helpers.ts b/test/test-helpers.ts index 0dfad59..ee52ad0 100644 --- a/test/test-helpers.ts +++ b/test/test-helpers.ts @@ -9,6 +9,10 @@ function getPrismaClient() { return new PrismaClient(); } +function getRandomInt(max: number = 10_000): number { + return Math.floor(Math.random() * max); +} + async function resetDatabase() { const prisma = getPrismaClient(); return prisma.task.deleteMany({}); @@ -16,5 +20,6 @@ async function resetDatabase() { export { getPrismaClient, + getRandomInt, resetDatabase };