From 75fa000f3721a2aa1b33dbbf84f8911849e030e1 Mon Sep 17 00:00:00 2001 From: David J Nevin <66497218+davidjnevin@users.noreply.github.com> Date: Tue, 30 Jan 2024 01:54:46 +0100 Subject: [PATCH 1/4] feat: add pydantic validation to endpoint in and out --- .../entrypoints/api/v1/route_cleaning.py | 30 ++++++++++++++--- .../entrypoints/api/v1/schemas_cleaning.py | 32 ++++++++++++++++++ tests/integrations/test_api.py | 33 +++++++++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 src/chatcleaner/adapters/entrypoints/api/v1/schemas_cleaning.py diff --git a/src/chatcleaner/adapters/entrypoints/api/v1/route_cleaning.py b/src/chatcleaner/adapters/entrypoints/api/v1/route_cleaning.py index 354b1fc..4464d9a 100644 --- a/src/chatcleaner/adapters/entrypoints/api/v1/route_cleaning.py +++ b/src/chatcleaner/adapters/entrypoints/api/v1/route_cleaning.py @@ -1,15 +1,22 @@ import json -from typing import Any +from typing import Any, Union from dependency_injector.wiring import Provide, inject from fastapi import APIRouter, Depends, Response +from chatcleaner.adapters.entrypoints.api.v1.schemas_cleaning import ( + AllCleaningsOut, + CleanedChatOut, + CleaningIn, + CleaningNotFound, + SingleCleaningOut, +) from chatcleaner.domain.ports.use_cases.clean import CleanUseCaseInterface router = APIRouter() -@router.get("/cleanings", response_model=None) +@router.get("/cleanings", response_model=AllCleaningsOut) @inject async def get_all_cleaning( use_case: CleanUseCaseInterface = Depends(Provide["cleaning_use_case"]), @@ -20,10 +27,25 @@ async def get_all_cleaning( ) -@router.get("/cleanings/{uuid}", response_model=None) +@router.get( + "/cleanings/{uuid}", response_model=Union[SingleCleaningOut, CleaningNotFound] +) @inject async def get_cleaning_by_uuid( uuid: str, use_case: CleanUseCaseInterface = Depends(Provide["cleaning_use_case"]), ) -> dict[str, Any]: - return use_case.get_by_uuid(uuid) + data = use_case.get_by_uuid(uuid) + return data + + +@router.post("/cleanings", response_model=Union[CleanedChatOut, None]) +@inject +async def clean_chat( + chat: CleaningIn, + use_case: CleanUseCaseInterface = Depends(Provide["cleaning_use_case"]), +): + data = use_case.clean(chat.body) + return Response( + content=json.dumps(data), media_type="application/json", status_code=201 + ) diff --git a/src/chatcleaner/adapters/entrypoints/api/v1/schemas_cleaning.py b/src/chatcleaner/adapters/entrypoints/api/v1/schemas_cleaning.py new file mode 100644 index 0000000..8123ccb --- /dev/null +++ b/src/chatcleaner/adapters/entrypoints/api/v1/schemas_cleaning.py @@ -0,0 +1,32 @@ +import datetime + +from pydantic import BaseModel + + +class CleaningIn(BaseModel): + body: str + + +class CleaningOut(BaseModel): + uuid: str + chat: str + cleaned_chat: str + created_at: datetime.datetime + updated_at: datetime.datetime + + +class AllCleaningsOut(BaseModel): + result: list[CleaningOut] + + +class CleaningNotFound(BaseModel): + result: str + + +class SingleCleaningOut(BaseModel): + result: CleaningOut + + +class CleanedChatOut(BaseModel): + uuid: str + cleaned_chat: str diff --git a/tests/integrations/test_api.py b/tests/integrations/test_api.py index 8591624..4fe4c07 100644 --- a/tests/integrations/test_api.py +++ b/tests/integrations/test_api.py @@ -1,4 +1,6 @@ +import fastapi import pytest +from fastapi.exceptions import HTTPException from httpx import AsyncClient from chatcleaner.adapters.entrypoints.api.app import app @@ -59,3 +61,34 @@ async def test_get_cleaning_by_uuid_async_api_with_fake_uuid_return_Not_found( assert response.status_code == 200 data = response.json() assert data["result"] == "Not found" + + +@pytest.mark.anyio +@pytest.mark.integration +async def test_clean_chat_endpoint_returns_201( + get_fake_container, + async_client: AsyncClient, + chat_text_with_times: str, + chat_text_without_times: str, +): + use_case = get_fake_container.cleaning_use_case() + with app.container.cleaning_use_case.override(use_case): + response = await async_client.post( + "/clean/cleanings", json={"body": chat_text_with_times} + ) + assert response.status_code == 201 + assert response.json()["uuid"] is not None + assert response.json()["cleaned_chat"] == chat_text_without_times + + +@pytest.mark.anyio +@pytest.mark.integration +async def test_clean_chat_endpoint_returns_error_if_max_length_is_exceeded( + get_fake_container, async_client: AsyncClient +): + use_case = get_fake_container.cleaning_use_case() + with app.container.cleaning_use_case.override(use_case): + response = await async_client.post( + "/clean/cleanings", json={"chat": "a" * 2001} + ) + assert response.status_code == 422 From 296f7b17e5e769430712dcb6711d6604f32225af Mon Sep 17 00:00:00 2001 From: David J Nevin <66497218+davidjnevin@users.noreply.github.com> Date: Tue, 30 Jan 2024 01:55:10 +0100 Subject: [PATCH 2/4] fix: blank space --- src/chatcleaner/adapters/entrypoints/api/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chatcleaner/adapters/entrypoints/api/app.py b/src/chatcleaner/adapters/entrypoints/api/app.py index ed7ebaa..bae63a7 100644 --- a/src/chatcleaner/adapters/entrypoints/api/app.py +++ b/src/chatcleaner/adapters/entrypoints/api/app.py @@ -33,6 +33,7 @@ def start_application(): app_.container = container include_router(app_) configure_cors(app_) + # start orm mappers try: start_mappers() From 7d30a34f135b847b749048ffa0de173b39bf8c02 Mon Sep 17 00:00:00 2001 From: David J Nevin <66497218+davidjnevin@users.noreply.github.com> Date: Tue, 30 Jan 2024 01:55:58 +0100 Subject: [PATCH 3/4] feat: add cleaned_chat and uuid to clean endpoint --- src/chatcleaner/adapters/use_cases/clean.py | 1 + tests/integrations/test_cleaning_use_case.py | 15 +++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/chatcleaner/adapters/use_cases/clean.py b/src/chatcleaner/adapters/use_cases/clean.py index 5a26cbe..a339383 100644 --- a/src/chatcleaner/adapters/use_cases/clean.py +++ b/src/chatcleaner/adapters/use_cases/clean.py @@ -28,6 +28,7 @@ def _clean(self, chat: str): model = cleaning_factory(**data_) self.uow.cleaning.add(model) self.uow.commit() + return {"uuid": model.uuid, "cleaned_chat": model.cleaned_chat} def _get_all(self) -> list[dict[str, list[str]]]: data_ = {"results": []} diff --git a/tests/integrations/test_cleaning_use_case.py b/tests/integrations/test_cleaning_use_case.py index 940f715..74981c6 100644 --- a/tests/integrations/test_cleaning_use_case.py +++ b/tests/integrations/test_cleaning_use_case.py @@ -7,7 +7,11 @@ def test_clean_use_case_clean(get_fake_container, get_clean_use_case): with Container.cleaning_uow.override(get_fake_container.cleaning_uow): with Container.chat_service.override(get_fake_container.chat_service): - get_clean_use_case.clean("\n19:10:00 from David to Everyone:\ntest") + result = get_clean_use_case.clean( + "\n19:10:00 from David to Everyone:\ntest" + ) + assert result["uuid"] is not None + assert result["cleaned_chat"] == "test" uow_ = get_fake_container.cleaning_uow() with uow_: result = uow_.cleaning.get_all() @@ -28,15 +32,6 @@ def test_clean_use_case_get_all(get_fake_container, get_clean_use_case): result = get_clean_use_case.get_all() # fixtures are scopes to module, so this should be 4 assert len(result["results"]) == 4 - # breakpoint() - # assert result[0].chat == "\n19:10:00 from David to Everyone:\ntest" - # assert result[0].cleaned_chat == "test" - # assert result[1].chat == "\n19:10:00 from David to Everyone:\ntest 1" - # assert result[1].cleaned_chat == "test 1" - # assert result[2].chat == "\n19:10:00 from David to Everyone:\ntest 2" - # assert result[2].cleaned_chat == "test 2" - # assert result[3].chat == "\n19:10:00 from David to Everyone:\ntest 3" - # assert result[3].cleaned_chat == "test 3" @pytest.mark.integration From dd7daf374e17191049fec8b5ebd52a650368e258 Mon Sep 17 00:00:00 2001 From: David J Nevin <66497218+davidjnevin@users.noreply.github.com> Date: Tue, 30 Jan 2024 01:56:41 +0100 Subject: [PATCH 4/4] test: add fixtures for caht with times and cleaned chat without times --- tests/conftest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index f81b485..02431bd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,6 +32,11 @@ def chat_text_with_times(): return "\n19:05:59 From David to Everyone:\nso far...\n19:35:48 From David to Everyone:\nOur highest priority is to satisfy the customer\nthrough early and continuous delivery\nof valuable software.\n19:36:59 From David to Everyone:\nthe highest, the lowest\n19:55:50 From David to Everyone:\nhttps://agilemanifesto.org/principles.html" +@pytest.fixture(scope="module") +def chat_text_without_times(): + return "so far...\nOur highest priority is to satisfy the customer\nthrough early and continuous delivery\nof valuable software.\nthe highest, the lowest\nhttps://agilemanifesto.org/principles.html" + + @pytest.fixture(scope="module") def get_fake_repository(): return FakeCleaningRepository()