From 4771d28c68a4d1c8eb325f9e0ef2ecafd5d2ea9d Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 15 Apr 2021 19:01:17 +0200 Subject: [PATCH] feat: add provider data conversion from document to entity and test in-memory gateway --- src/api/routers/todos/index.ts | 4 +- src/cli/commands/todos/index.ts | 4 +- src/modules/todos/entities/todo.spec.ts | 2 +- .../todos/impl/create-todo.impl.spec.ts | 5 +- .../todos/impl/delete-todo.impl.spec.ts | 4 +- .../todos/impl/list-todos.impl.spec.ts | 5 +- .../{ => todo-http}/todo-http.gateway.ts | 4 +- .../todo-in-memory/todo-document.spec.ts | 51 +++++++++++++ src/providers/todo-in-memory/todo-document.ts | 18 +++++ .../todo-in-memory.gateway.spec.ts | 75 +++++++++++++++++++ .../todo-in-memory.gateway.ts | 20 ++--- src/providers/{ => uuid}/v4-uuid.ts | 4 +- 12 files changed, 165 insertions(+), 31 deletions(-) rename src/providers/{ => todo-http}/todo-http.gateway.ts (95%) create mode 100644 src/providers/todo-in-memory/todo-document.spec.ts create mode 100644 src/providers/todo-in-memory/todo-document.ts create mode 100644 src/providers/todo-in-memory/todo-in-memory.gateway.spec.ts rename src/providers/{ => todo-in-memory}/todo-in-memory.gateway.ts (62%) rename src/providers/{ => uuid}/v4-uuid.ts (69%) diff --git a/src/api/routers/todos/index.ts b/src/api/routers/todos/index.ts index 1931531..c702eed 100644 --- a/src/api/routers/todos/index.ts +++ b/src/api/routers/todos/index.ts @@ -1,8 +1,8 @@ import { Router } from "express"; // Gatway Implementations -import InMemoryTodoGateway from "../../../providers/todo-in-memory.gateway"; -import V4UuidGenerator from "../../../providers/v4-uuid"; +import InMemoryTodoGateway from "../../../providers/todo-in-memory/todo-in-memory.gateway"; +import V4UuidGenerator from "../../../providers/uuid/v4-uuid"; // Use Case Implementations import CreateTodoImpl from "../../../modules/todos/impl/create-todo.impl"; diff --git a/src/cli/commands/todos/index.ts b/src/cli/commands/todos/index.ts index 872413b..a9b4964 100644 --- a/src/cli/commands/todos/index.ts +++ b/src/cli/commands/todos/index.ts @@ -7,8 +7,8 @@ import DeleteTodoImpl from "../../../modules/todos/impl/delete-todo.impl"; import ListTodosImpl from "../../../modules/todos/impl/list-todos.impl"; // Providers -import RestTodoGateway from "../../../providers/todo-http.gateway"; -import V4UuidGenerator from "../../../providers/v4-uuid"; +import RestTodoGateway from "../../../providers/todo-http/todo-http.gateway"; +import V4UuidGenerator from "../../../providers/uuid/v4-uuid"; // Subcommands import listCmd from "./list-todos"; diff --git a/src/modules/todos/entities/todo.spec.ts b/src/modules/todos/entities/todo.spec.ts index c20c7e0..1dc12fa 100644 --- a/src/modules/todos/entities/todo.spec.ts +++ b/src/modules/todos/entities/todo.spec.ts @@ -17,7 +17,7 @@ describe("[Todo] Test Cases", () => { } }) - it("should throw en error because of a missing ID", () => { + it("should throw en error because of a missing Description", () => { try { const _ = new Todo("id", notDefined, new Date()) } catch (error) { diff --git a/src/modules/todos/impl/create-todo.impl.spec.ts b/src/modules/todos/impl/create-todo.impl.spec.ts index fbc52ca..d6cdeca 100644 --- a/src/modules/todos/impl/create-todo.impl.spec.ts +++ b/src/modules/todos/impl/create-todo.impl.spec.ts @@ -27,8 +27,6 @@ describe("[CreateTodo] Success Cases", () => { const createTodo: CreateTodoImpl = new CreateTodoImpl(mockSuccessGateway, mockUUIDGenerator); it("should return a the mocked todo in a valid CreateTodoResponse object", async () => { - expect.assertions(1); - const insufficientRequest: any = { ...request }; delete insufficientRequest.id @@ -36,6 +34,7 @@ describe("[CreateTodo] Success Cases", () => { const response = await createTodo.execute(insufficientRequest) expect(response).toEqual({ ...insufficientRequest, id: "generatedId" }); + expect.assertions(1); }) }) @@ -44,11 +43,11 @@ describe("[CreateTodo] Fail Cases", () => { const createTodo: CreateTodoImpl = new CreateTodoImpl(mockFailureGateway, mockUUIDGenerator); it("should return throw an error with the gateways message", async () => { - expect.assertions(1); try { await createTodo.execute(request) } catch (error) { expect(error.message).toEqual(errorMessage) + expect.assertions(1); } }) }) diff --git a/src/modules/todos/impl/delete-todo.impl.spec.ts b/src/modules/todos/impl/delete-todo.impl.spec.ts index 7f57b88..ba2614d 100644 --- a/src/modules/todos/impl/delete-todo.impl.spec.ts +++ b/src/modules/todos/impl/delete-todo.impl.spec.ts @@ -16,11 +16,11 @@ describe("[DeleteTodo] Success Cases", () => { const deleteTodo: DeleteTodoImpl = new DeleteTodoImpl(mockSuccessGateway); it("should return a the mocked todo in a valid DeleteTodoResponse object", async () => { - expect.assertions(1); const result = await deleteTodo.execute({ id: randomId }) expect(result).toEqual({ item: todo }); + expect.assertions(1); }) }) @@ -36,11 +36,11 @@ describe("[DeleteTodo] Fail Cases", () => { const deleteTodo: DeleteTodoImpl = new DeleteTodoImpl(mockFailureGateway); it("should return throw an error with the gateways message", async () => { - expect.assertions(1); try { await deleteTodo.execute({ id: randomId }) } catch (error) { expect(error.message).toEqual(errorMessage) + expect.assertions(1); } }) }) diff --git a/src/modules/todos/impl/list-todos.impl.spec.ts b/src/modules/todos/impl/list-todos.impl.spec.ts index 4fd2f14..5736aa8 100644 --- a/src/modules/todos/impl/list-todos.impl.spec.ts +++ b/src/modules/todos/impl/list-todos.impl.spec.ts @@ -16,11 +16,10 @@ describe("[ListTodos] Success Cases", () => { const listTodos: ListTodosImpl = new ListTodosImpl(mockSuccessGateway); it("should return a the mocked todo in a valid ListTodosResponse object", async () => { - expect.assertions(1); - const result = await listTodos.execute(request) expect(result).toEqual({ items: [todo], count: 1 }); + expect.assertions(1); }) }) @@ -35,11 +34,11 @@ describe("[ListTodos] Fail Cases", () => { const listTodos: ListTodosImpl = new ListTodosImpl(mockFailureGateway); it("should return throw an error with the gateways message", async () => { - expect.assertions(1); try { await listTodos.execute(request) } catch (error) { expect(error.message).toEqual(errorMessage) + expect.assertions(1); } }) }) diff --git a/src/providers/todo-http.gateway.ts b/src/providers/todo-http/todo-http.gateway.ts similarity index 95% rename from src/providers/todo-http.gateway.ts rename to src/providers/todo-http/todo-http.gateway.ts index c86bf8e..4957bdb 100644 --- a/src/providers/todo-http.gateway.ts +++ b/src/providers/todo-http/todo-http.gateway.ts @@ -1,6 +1,6 @@ import { ClientRequest, IncomingMessage, RequestOptions } from "http"; -import { ReadableGateway, WritableGateway } from "../modules/shared/entity.gateway"; -import { Todo } from "../modules/todos/entities/todo"; +import { ReadableGateway, WritableGateway } from "../../modules/shared/entity.gateway"; +import { Todo } from "../../modules/todos/entities/todo"; interface RestListTodoResponse { count: number; diff --git a/src/providers/todo-in-memory/todo-document.spec.ts b/src/providers/todo-in-memory/todo-document.spec.ts new file mode 100644 index 0000000..d6e3d6f --- /dev/null +++ b/src/providers/todo-in-memory/todo-document.spec.ts @@ -0,0 +1,51 @@ +import { Todo } from "../../modules/todos/entities/todo"; + +import TodoDocument from "./todo-document"; + +const notDefined: any = undefined; +const description = "description"; +const todo = new Todo("id", "description", new Date()) + +describe("[TodoDocument] Test Cases", () => { + + it("should create a document successfully", () => { + const document = new TodoDocument("description", new Date().toISOString()) + expect(document).toBeDefined() + }) + + it("should create a document from todo successfully", () => { + const document = TodoDocument.fromTodo(todo) + expect(document.description).toEqual(description) + expect(document.due).toEqual(todo.due.toISOString()) + }) + + it("should convert a document to todo successfully", () => { + const document = TodoDocument.fromTodo(todo) + expect(document.toEntity("id")).toEqual(todo) + }) + + it("should throw en error because of an undefined Todo", () => { + try { + const _ = TodoDocument.fromTodo(notDefined) + } catch (error) { + expect(error.message).toEqual("ValidationError: No todo provided!") + } + }) + + it("should throw en error because of a missing Description", () => { + try { + const _ = new TodoDocument(notDefined, new Date().toISOString()) + } catch (error) { + expect(error.message).toEqual("ValidationError: Description not provided!") + } + }) + + it("should throw en error because of a missing Due date", () => { + try { + const _ = new TodoDocument("description", notDefined) + } catch (error) { + expect(error.message).toEqual("ValidationError: Due date not provided!") + } + }) + +}) diff --git a/src/providers/todo-in-memory/todo-document.ts b/src/providers/todo-in-memory/todo-document.ts new file mode 100644 index 0000000..e9d0a3b --- /dev/null +++ b/src/providers/todo-in-memory/todo-document.ts @@ -0,0 +1,18 @@ +import { Todo } from "../../modules/todos/entities/todo"; + +export default class TodoDocument { + + public static fromTodo(todo: Todo): TodoDocument { + if (!todo) throw new Error("ValidationError: No todo provided!") + return new TodoDocument(todo.description, todo.due.toISOString()); + } + + constructor(public readonly description: string, public readonly due: string) { + if (!description) throw new Error("ValidationError: Description not provided!") + if (!due) throw new Error("ValidationError: Due date not provided!") + } + + public toEntity(id: string): Todo { + return new Todo(id, this.description, new Date(this.due)); + } +} diff --git a/src/providers/todo-in-memory/todo-in-memory.gateway.spec.ts b/src/providers/todo-in-memory/todo-in-memory.gateway.spec.ts new file mode 100644 index 0000000..3f06657 --- /dev/null +++ b/src/providers/todo-in-memory/todo-in-memory.gateway.spec.ts @@ -0,0 +1,75 @@ +import { Todo } from "../../modules/todos/entities/todo"; +import TodoDocument from "./todo-document"; +import InMemoryTodoGateway from "./todo-in-memory.gateway"; + +describe("[InMemoryTodoGateway] Initialization", () => { + it("should create an in memory todo gateway without any todos", async () => { + const inMemTodoGW = new InMemoryTodoGateway() + const todos = await inMemTodoGW.find({ limit: 10, skip: 0 }) + expect(todos.length).toEqual(0) + expect.assertions(1) + }) +}) + +describe("[InMemoryTodoGateway] Test Cases", () => { + + let inMemTodoGW: InMemoryTodoGateway + + beforeEach(() => { + const seed = new Map([ + ["1", new TodoDocument("first", new Date().toISOString())], + ["2", new TodoDocument("second", new Date().toISOString())], + ["3", new TodoDocument("third", new Date().toISOString())], + ]) + + inMemTodoGW = new InMemoryTodoGateway(seed); + }) + + it("should return all items in map as todos", async () => { + const todos = await inMemTodoGW.find({ limit: 10, skip: 0 }) + expect(todos.length).toEqual(3) + expect.assertions(1) + }) + + it("should return second todo", async () => { + expect.assertions(2) + const todos = await inMemTodoGW.find({ limit: 1, skip: 1 }) + expect(todos.length).toEqual(1) + expect(todos[0].id).toEqual("2") + }) + + it("should add a new todo and return it", async () => { + const todo = await inMemTodoGW.save(new Todo("4", "forth", new Date())); + const todos = await inMemTodoGW.find({ limit: 1, skip: 3 }) + expect(todos.length).toEqual(1) + expect(todos[0]).toEqual(todo) + expect.assertions(2) + }) + + it("should throw an error because the todo already exist", async () => { + try { + const _ = await inMemTodoGW.save(new Todo("1", "description", new Date())); + } catch (error) { + expect(error.message).toEqual("Todo already exists!"); + expect.assertions(1) + } + }) + + it("should remove a todo and return its value", async () => { + const todo = await inMemTodoGW.delete("3"); + const todos = await inMemTodoGW.find({ limit: 10, skip: 0 }) + expect(todos).not.toContain(todo) + expect(todos.length).toEqual(2) + expect.assertions(2) + }) + + it("should throw an error because the todo does not exist", async () => { + try { + const _ = await inMemTodoGW.delete("4"); + } catch (error) { + expect(error.message).toEqual("Todo does not exist!"); + expect.assertions(1) + } + }) + +}) diff --git a/src/providers/todo-in-memory.gateway.ts b/src/providers/todo-in-memory/todo-in-memory.gateway.ts similarity index 62% rename from src/providers/todo-in-memory.gateway.ts rename to src/providers/todo-in-memory/todo-in-memory.gateway.ts index 484765d..57007b2 100644 --- a/src/providers/todo-in-memory.gateway.ts +++ b/src/providers/todo-in-memory/todo-in-memory.gateway.ts @@ -1,23 +1,15 @@ -import { ReadableGateway, WritableGateway } from "../modules/shared/entity.gateway"; -import { Todo } from "../modules/todos/entities/todo"; +import { ReadableGateway, WritableGateway } from "../../modules/shared/entity.gateway"; +import { Todo } from "../../modules/todos/entities/todo"; +import TodoDocument from "./todo-document"; interface FindQuery { limit: number; skip: number; } -interface TodoDocument { - description: string, - due: string; -} - export default class InMemoryTodoGateway implements ReadableGateway, WritableGateway { - private documents: Map; - - constructor() { - this.documents = new Map() - } + constructor(private documents: Map = new Map()) {} public async delete(id: string): Promise { if (!this.documents.has(id)) throw new Error("Todo does not exist!"); @@ -32,7 +24,7 @@ export default class InMemoryTodoGateway implements ReadableGateway, Writa public async save(todo: Todo): Promise { if (this.documents.has(todo.id)) throw new Error("Todo already exists!"); - this.documents.set(todo.id, { description: todo.description, due: todo.due.toISOString() }); + this.documents.set(todo.id, TodoDocument.fromTodo(todo)); return todo; } @@ -44,6 +36,6 @@ export default class InMemoryTodoGateway implements ReadableGateway, Writa const docs = keys.map(key => this.documents.get(key)!); - return docs.map((d, i) => new Todo(keys[i], d.description, new Date(d.due))) + return docs.map((d, i) => d.toEntity(keys[i])) } } diff --git a/src/providers/v4-uuid.ts b/src/providers/uuid/v4-uuid.ts similarity index 69% rename from src/providers/v4-uuid.ts rename to src/providers/uuid/v4-uuid.ts index cc6995d..e657332 100644 --- a/src/providers/v4-uuid.ts +++ b/src/providers/uuid/v4-uuid.ts @@ -1,4 +1,4 @@ -import UUDIGenerator from "../modules/shared/uuid-generator"; +import UUDIGenerator from "../../modules/shared/uuid-generator"; import { v4 } from "uuid"; @@ -6,4 +6,4 @@ export default class V4UuidGenerator implements UUDIGenerator { public generate(): string { return v4() } -} \ No newline at end of file +}