Skip to content

Commit

Permalink
feat: implement new use case
Browse files Browse the repository at this point in the history
As a student, I want to update a registered task so that I can change its details.

This commit includes:

- Implementation of the use case for updating a task
- Implementation of unit tests related to the use case and adapters
  • Loading branch information
Leonardo Giraldi Moreno Giuranno committed May 19, 2024
1 parent 12b7926 commit a143e7f
Show file tree
Hide file tree
Showing 31 changed files with 630 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/adapters/task/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./createTask";
export * from "./listTaskById";
export * from "./updateTask";
1 change: 1 addition & 0 deletions src/adapters/task/updateTask/boundaries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./updateTask.controller.input.boundary";
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {
UpdateTaskRequestModel,
UpdateTaskResponseModel,
} from "@/useCases/task";

interface UpdateTaskControllerInputBoundary {
update(
requestModel: UpdateTaskRequestModel
): Promise<UpdateTaskResponseModel>;
}

export { UpdateTaskControllerInputBoundary };
1 change: 1 addition & 0 deletions src/adapters/task/updateTask/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./updateTask.controller";
22 changes: 22 additions & 0 deletions src/adapters/task/updateTask/controllers/updateTask.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UpdateTaskControllerInputBoundary } from "..";
import {
UpdateTaskInputBoundary,
UpdateTaskRequestModel,
UpdateTaskResponseModel,
} from "@/useCases/task";

class UpdateTaskController implements UpdateTaskControllerInputBoundary {
private inputBoundary: UpdateTaskInputBoundary;

constructor(inputBoundary: UpdateTaskInputBoundary) {
this.inputBoundary = inputBoundary;
}

async update(
requestModel: UpdateTaskRequestModel
): Promise<UpdateTaskResponseModel> {
return await this.inputBoundary.update(requestModel);
}
}

export { UpdateTaskController };
3 changes: 3 additions & 0 deletions src/adapters/task/updateTask/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./boundaries";
export * from "./controllers";
export * from "./presenters";
1 change: 1 addition & 0 deletions src/adapters/task/updateTask/presenters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./updateTask.presenter";
44 changes: 44 additions & 0 deletions src/adapters/task/updateTask/presenters/updateTask.presenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
InvalidAttributeException,
ResourceNotFoundException,
} from "@/adapters/exceptions";
import {
UpdateTaskInvalidAttributeException,
UpdateTaskNotFoundException,
UpdateTaskOutputBoundary,
UpdateTaskResponseModel,
} from "@/useCases/task";

class UpdateTaskPresenter implements UpdateTaskOutputBoundary {
presentSuccess(
responseModel: UpdateTaskResponseModel
): UpdateTaskResponseModel {
return responseModel;
}

presentUpdateTaskInvalidTitle(
error: UpdateTaskInvalidAttributeException
): UpdateTaskResponseModel {
throw new InvalidAttributeException(error.message);
}

presentUpdateTaskInvalidDueDate(
error: UpdateTaskInvalidAttributeException
): UpdateTaskResponseModel {
throw new InvalidAttributeException(error.message);
}

presentUpdateTaskInvalidDescription(
error: UpdateTaskInvalidAttributeException
): UpdateTaskResponseModel {
throw new InvalidAttributeException(error.message);
}

presentUpdateTaskNotFound(
error: UpdateTaskNotFoundException
): UpdateTaskResponseModel {
throw new ResourceNotFoundException(error.message);
}
}

export { UpdateTaskPresenter };
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { updateTaskInputBoundaryMock } from "@/tests/unit/mocks/task";

import { UpdateTaskController } from "@/adapters/task";
import {
UpdateTaskRequestModel,
UpdateTaskResponseModel,
} from "@/useCases/task";

describe("UpdateTaskController", () => {
let controller: UpdateTaskController;

let requestModel: UpdateTaskRequestModel;
let responseModel: UpdateTaskResponseModel;

beforeEach(() => {
jest.clearAllMocks();

controller = new UpdateTaskController(updateTaskInputBoundaryMock);

requestModel = new UpdateTaskRequestModel({
id: "1",
description: "Task 1 description",
dueDate: new Date("2022-01-01T00:00:00.000Z"),
title: "Task 1 title",
priority: "low",
status: "todo",
});
responseModel = new UpdateTaskResponseModel({
id: "1",
description: "Task 1 description",
dueDate: new Date("2022-01-01T00:00:00.000Z"),
title: "Task 1 title",
priority: "low",
status: "todo",
});
});

describe("update", () => {
it("should return response model", async () => {
updateTaskInputBoundaryMock.update.mockResolvedValue(responseModel);

const result = await controller.update(requestModel);

expect(result).toEqual(responseModel);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { UpdateTaskPresenter } from "@/adapters/task";
import {
InvalidAttributeException,
ResourceNotFoundException,
} from "@/adapters/exceptions";

import {
UpdateTaskResponseModel,
UpdateTaskInvalidAttributeException,
} from "@/useCases/task";

describe("UpdateTaskPresenter", () => {
let presenter: UpdateTaskPresenter;

let responseModel: UpdateTaskResponseModel;

beforeEach(() => {
jest.clearAllMocks();

presenter = new UpdateTaskPresenter();
responseModel = new UpdateTaskResponseModel({
id: "1",
description: "Task 1 description",
dueDate: new Date("2022-01-01T00:00:00.000Z"),
title: "Task 1 title",
priority: "low",
status: "todo",
});
});

describe("presentSuccess", () => {
it("should return response model", async () => {
expect(presenter.presentSuccess(responseModel)).toEqual(
responseModel
);
});
});

describe("presentUpdateTaskInvalidTitle", () => {
it("should throw InvalidAttributeException", async () => {
const error = new UpdateTaskInvalidAttributeException(
"Invalid title"
);

expect(() =>
presenter.presentUpdateTaskInvalidTitle(error)
).toThrow(new InvalidAttributeException(error.message));
});
});

describe("presentUpdateTaskInvalidDueDate", () => {
it("should throw InvalidAttributeException", async () => {
const error = new UpdateTaskInvalidAttributeException(
"Invalid due date"
);

expect(() =>
presenter.presentUpdateTaskInvalidDueDate(error)
).toThrow(new InvalidAttributeException(error.message));
});
});

describe("presentUpdateTaskInvalidDescription", () => {
it("should throw InvalidAttributeException", async () => {
const error = new UpdateTaskInvalidAttributeException(
"Invalid description"
);

expect(() =>
presenter.presentUpdateTaskInvalidDescription(error)
).toThrow(new InvalidAttributeException(error.message));
});
});

describe("presentUpdateTaskNotFound", () => {
it("should throw ResourceNotFoundException", async () => {
const error = new ResourceNotFoundException("Task not found");

expect(() => presenter.presentUpdateTaskNotFound(error)).toThrow(
new ResourceNotFoundException(error.message)
);
});
});
});
3 changes: 3 additions & 0 deletions src/tests/unit/mocks/task/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ export * from "./createTask.input.boundary.mock";
export * from "./listTaskById.ds.gateway.mock";
export * from "./listTaskById.output.boundary.mock";
export * from "./listTaskById.input.boundary.mock";
export * from "./updateTask.ds.gateway.mock";
export * from "./updateTask.output.boundary.mock";
export * from "./updateTask.input.boundary.mock";
6 changes: 6 additions & 0 deletions src/tests/unit/mocks/task/updateTask.ds.gateway.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const updateTaskDsGatewayMock = {
update: jest.fn(),
getById: jest.fn(),
};

export { updateTaskDsGatewayMock };
5 changes: 5 additions & 0 deletions src/tests/unit/mocks/task/updateTask.input.boundary.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const updateTaskInputBoundaryMock = {
update: jest.fn(),
};

export { updateTaskInputBoundaryMock };
9 changes: 9 additions & 0 deletions src/tests/unit/mocks/task/updateTask.output.boundary.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const updateTaskOutputBoundaryMock = {
presentSuccess: jest.fn(),
presentUpdateTaskInvalidTitle: jest.fn(),
presentUpdateTaskInvalidDueDate: jest.fn(),
presentUpdateTaskInvalidDescription: jest.fn(),
presentUpdateTaskNotFound: jest.fn(),
};

export { updateTaskOutputBoundaryMock };
125 changes: 125 additions & 0 deletions src/tests/unit/useCases/task/updateTask.interactor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {
updateTaskDsGatewayMock,
updateTaskOutputBoundaryMock,
} from "@/tests/unit/mocks/task";

import {
UpdateTaskInteractor,
UpdateTaskDsResponseModel,
UpdateTaskRequestModel,
UpdateTaskResponseModel,
} from "@/useCases/task";

describe("UpdateTaskInteractor", () => {
let interactor: UpdateTaskInteractor;

let requestModel: UpdateTaskRequestModel;
let responseModel: UpdateTaskResponseModel;
let dsResponseModel: UpdateTaskDsResponseModel;
let dateMock: Date;

beforeEach(() => {
jest.clearAllMocks();

dateMock = new Date("2020-01-01T00:00:00.000Z");
jest.useFakeTimers().setSystemTime(dateMock);

interactor = new UpdateTaskInteractor(
updateTaskDsGatewayMock,
updateTaskOutputBoundaryMock
);
requestModel = {
id: "1",
description: "Task 1 description",
title: "Task 1 title",
dueDate: new Date("2020-01-02T00:00:00.000Z"),
priority: "low",
status: "todo",
};
responseModel = {
id: "1",
description: "Task 1 description",
title: "Task 1 title",
dueDate: new Date("2020-01-02T00:00:00.000Z"),
priority: "low",
status: "todo",
};
dsResponseModel = {
id: "1",
description: "Task 1 description",
title: "Task 1 title",
dueDate: new Date("2020-01-02T00:00:00.000Z"),
priority: "low",
status: "todo",
};
});

describe("create", () => {
beforeEach(() => {
updateTaskDsGatewayMock.update.mockResolvedValue(dsResponseModel);
updateTaskDsGatewayMock.getById.mockResolvedValue(dsResponseModel);

updateTaskOutputBoundaryMock.presentSuccess.mockReturnValue(
responseModel
);
updateTaskOutputBoundaryMock.presentUpdateTaskInvalidTitle.mockImplementation(
() => {
throw new Error("Invalid title");
}
);
updateTaskOutputBoundaryMock.presentUpdateTaskInvalidDueDate.mockImplementation(
() => {
throw new Error("Invalid due date");
}
);
updateTaskOutputBoundaryMock.presentUpdateTaskInvalidDescription.mockImplementation(
() => {
throw new Error("Invalid description");
}
);
updateTaskOutputBoundaryMock.presentUpdateTaskNotFound.mockImplementation(
() => {
throw new Error("Task not found");
}
);
});

it("should return success", async () => {
const result = await interactor.update(requestModel);

expect(result).toEqual(responseModel);
});

it("should return exception when title is invalid", async () => {
requestModel.title = "";

await expect(interactor.update(requestModel)).rejects.toThrow(
"Invalid title"
);
});

it("should return exception when dueDate is invalid", async () => {
requestModel.dueDate = new Date("2019-12-31T00:00:00.000Z");

await expect(interactor.update(requestModel)).rejects.toThrow(
"Invalid due date"
);
});

it("should return exception when description is invalid", async () => {
requestModel.description = "";

await expect(interactor.update(requestModel)).rejects.toThrow(
"Invalid description"
);
});

it("should return exception when task is not found", async () => {
updateTaskDsGatewayMock.getById.mockResolvedValue(null);

await expect(interactor.update(requestModel)).rejects.toThrow(
"Task not found"
);
});
});
});
1 change: 1 addition & 0 deletions src/useCases/task/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./createTask";
export * from "./listTaskById";
export * from "./updateTask";
2 changes: 2 additions & 0 deletions src/useCases/task/updateTask/boundaries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./updateTask.input.boundary";
export * from "./updateTask.output.boundary";
Loading

0 comments on commit a143e7f

Please sign in to comment.