Skip to content

Commit

Permalink
✨ feat: add DELETE /tasks/:id route
Browse files Browse the repository at this point in the history
  • Loading branch information
jbuget committed Feb 9, 2022
1 parent eeb6484 commit b29cecd
Show file tree
Hide file tree
Showing 12 changed files with 109 additions and 17 deletions.
4 changes: 2 additions & 2 deletions src/domain/entities/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/domain/entities/TaskRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ export interface TaskRepository {

save(task: Task): Promise<Task>;

findById(id: number): Promise<Task|null>;
findById(id: number): Promise<Task | null>;

delete(task: Task): Promise<void>;
}
2 changes: 1 addition & 1 deletion src/domain/usecases/commands/create_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Status, Task } from '../../entities/Task';
async function createTask(params: any, taskRepository: TaskRepository): Promise<Task> {
const now = new Date();
const task = new Task({
id: null,
id: undefined,
content: params.content,
createdAt: now,
updatedAt: now,
Expand Down
13 changes: 13 additions & 0 deletions src/domain/usecases/commands/delete_task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { TaskRepository } from '../../entities/TaskRepository';
import { Task } from '../../entities/Task';

async function deleteTask(id: number, taskRepository: TaskRepository): Promise<void> {
const task: Task | null = await taskRepository.findById(id);
if (task) {
await taskRepository.delete(task);
}
}

export {
deleteTask
};
10 changes: 7 additions & 3 deletions src/infrastructure/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -31,7 +32,7 @@ function build(): FastifyInstance {

// Get a task
server.get('/tasks/:id', async function (request: FastifyRequest<any>, reply: FastifyReply<any>) {
const taskId = parseInt(request.params.id);
const taskId: number = parseInt(request.params.id);
if (isNaN(taskId)) {
reply.code(429).send();
} else {
Expand Down Expand Up @@ -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<any>, reply: FastifyReply<any>) {
const taskId: number = parseInt(request.params.id);
const taskRepository: TaskRepository = container.resolve('taskRepository');
await deleteTask(taskId, taskRepository);
reply.code(204).send();
});

return server;
Expand Down
12 changes: 12 additions & 0 deletions src/infrastructure/repositories/TaskRepositorySql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,16 @@ export class TaskRepositorySql implements TaskRepository {
}
return null;
}

async delete(task: Task): Promise<void> {
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();
}
}
}
8 changes: 4 additions & 4 deletions test/domain/models/Task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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'
Expand All @@ -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'
};
Expand Down
3 changes: 3 additions & 0 deletions test/domain/usecases/commands/create_task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
return Promise.resolve();
},
async findById(id: number): Promise<Task | null> {
return null;
},
Expand Down
3 changes: 3 additions & 0 deletions test/domain/usecases/queries/list_tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
return Promise.resolve();
},
async findById(id: number): Promise<Task | null> {
return null;
},
Expand Down
34 changes: 30 additions & 4 deletions test/infrastructure/app.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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'),
Expand All @@ -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'
});
Expand All @@ -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();
});
});
});
});
28 changes: 26 additions & 2 deletions test/infrastructure/repositories/TaskRepositorySql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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();
});
});
});
5 changes: 5 additions & 0 deletions test/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ 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({});
}

export {
getPrismaClient,
getRandomInt,
resetDatabase
};

0 comments on commit b29cecd

Please sign in to comment.