Skip to content
Merged
10 changes: 10 additions & 0 deletions V2/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,13 @@ functions:
- POST
route: time-entries/
authLevel: anonymous

delete_time_entry:
handler: time_tracker/time_entries/interface.delete_time_entry
events:
- http: true
x-azure-settings:
methods:
- DELETE
route: time-entries/{id}
authLevel: anonymous
50 changes: 50 additions & 0 deletions V2/tests/api/azure/time_entry_azure_endpoints_test.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import pytest
import json

import azure.functions as func

import time_tracker.time_entries._application._time_entries as azure_time_entries
from time_tracker._infrastructure import DB
from time_tracker.time_entries import _domain as domain_time_entries
from time_tracker.time_entries import _infrastructure as infrastructure_time_entries


TIME_ENTRY_URL = "/api/time-entries/"


@pytest.fixture(name='insert_time_entry')
def _insert_time_entry() -> domain_time_entries.TimeEntry:
def _new_time_entry(time_entry: domain_time_entries.TimeEntry, database: DB):
dao = infrastructure_time_entries.TimeEntriesSQLDao(database)
new_time_entry = dao.create(time_entry)
return new_time_entry
return _new_time_entry


def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_all_attributes(
test_db, time_entry_factory, activity_factory, insert_activity
):
Expand All @@ -26,3 +40,39 @@ def test__time_entry_azure_endpoint__creates_an_time_entry__when_time_entry_has_

assert response.status_code == 201
assert time_entry_json_data == time_entry_body


def test__delete_time_entries_azure_endpoint__returns_an_time_entry_with_true_deleted__when_its_id_is_found(
test_db, time_entry_factory, insert_time_entry, insert_activity, activity_factory,
):
inserted_activity = insert_activity(activity_factory(), test_db).__dict__
time_entry_body = time_entry_factory(activity_id=inserted_activity["id"], technologies="[jira,sql]")
inserted_time_entry = insert_time_entry(time_entry_body, test_db)

req = func.HttpRequest(
method='DELETE',
body=None,
url=TIME_ENTRY_URL,
route_params={"id": inserted_time_entry.id},
)

response = azure_time_entries._delete_time_entry.delete_time_entry(req)
time_entry_json_data = json.loads(response.get_body().decode("utf-8"))

assert response.status_code == 200
assert time_entry_json_data['deleted'] is True


def test__delete_time_entries_azure_endpoint__returns_a_status_code_400__when_time_entry_recive_invalid_id(
):
req = func.HttpRequest(
method="DELETE",
body=None,
url=TIME_ENTRY_URL,
route_params={"id": "invalid id"},
)

response = azure_time_entries._delete_time_entry.delete_time_entry(req)

assert response.status_code == 400
assert response.get_body() == b'Invalid Format ID'
6 changes: 5 additions & 1 deletion V2/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
@pytest.fixture(name='activity_factory')
def _activity_factory() -> activities_domain.Activity:
def _make_activity(
name: str = Faker().name(), description: str = Faker().sentence(), deleted: bool = False, status: int = 1
name: str = Faker().name(),
description: str = Faker().sentence(),
deleted: bool = False,
status: int = 1,
):
activity = activities_domain.Activity(
id=None,
Expand All @@ -20,6 +23,7 @@ def _make_activity(
status=status
)
return activity

return _make_activity


Expand Down
25 changes: 24 additions & 1 deletion V2/tests/integration/daos/time_entries_dao_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest

from faker import Faker

import time_tracker.time_entries._domain as domain
import time_tracker.time_entries._infrastructure as infrastructure
Expand Down Expand Up @@ -46,3 +46,26 @@ def test__time_entry__returns_None__when_not_saves_correctly(
inserted_time_entry = dao.create(time_entry_to_insert)

assert inserted_time_entry is None


def test_delete__returns_an_time_entry_with_true_deleted__when_an_time_entry_matching_its_id_is_found(
create_fake_dao, test_db, time_entry_factory, insert_activity, activity_factory
):
dao = create_fake_dao(test_db)
inserted_activity = insert_activity(activity_factory(), dao.db)
existent_time_entry = time_entry_factory(activity_id=inserted_activity.id, technologies="[jira,sql]")
inserted_time_entry = dao.create(existent_time_entry)

result = dao.delete(inserted_time_entry.id)

assert result.deleted is True


def test_delete__returns_none__when_no_time_entry_matching_its_id_is_found(
create_fake_dao, test_db
):
dao = create_fake_dao(test_db)

result = dao.delete(Faker().pyint())

assert result is None
17 changes: 17 additions & 0 deletions V2/tests/unit/services/time_entry_service_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from faker import Faker

from time_tracker.time_entries._domain import TimeEntryService


Expand All @@ -12,3 +14,18 @@ def test__create_time_entries__uses_the_time_entry_dao__to_create_an_time_entry(

assert time_entry_dao.create.called
assert expected_time_entry == actual_time_entry


def test__delete_time_entry__uses_the_time_entry_dao__to_change_time_entry_deleted(
mocker,
):
expected_time_entry = mocker.Mock()
time_entry_dao = mocker.Mock(
delete=mocker.Mock(return_value=expected_time_entry)
)

time_entry_service = TimeEntryService(time_entry_dao)
deleted_time_entry = time_entry_service.delete(Faker().pyint())

assert time_entry_dao.delete.called
assert expected_time_entry == deleted_time_entry
14 changes: 14 additions & 0 deletions V2/tests/unit/use_cases/time_entries_use_case_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pytest_mock import MockFixture
from faker import Faker

from time_tracker.time_entries._domain import _use_cases

Expand All @@ -16,3 +17,16 @@ def test__create_time_entry_function__uses_the_time_entries_service__to_create_t

assert time_entry_service.create.called
assert expected_time_entry == actual_time_entry


def test__delete_time_entry_function__uses_the_time_entry_service__to_change_time_entry_deleted(
mocker: MockFixture,
):
expected_time_entry = mocker.Mock()
time_entry_service = mocker.Mock(delete=mocker.Mock(return_value=expected_time_entry))

time_entry_use_case = _use_cases.DeleteTimeEntryUseCase(time_entry_service)
deleted_time_entry = time_entry_use_case.delete_time_entry(Faker().pyint())

assert time_entry_service.delete.called
assert expected_time_entry == deleted_time_entry
2 changes: 1 addition & 1 deletion V2/time_tracker/time_entries/_application/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# flake8: noqa
from ._time_entries import create_time_entry
from ._time_entries import create_time_entry, delete_time_entry
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# flake8: noqa
from ._create_time_entry import create_time_entry
from ._create_time_entry import create_time_entry
from ._delete_time_entry import delete_time_entry
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import json

import azure.functions as func

from ... import _domain
from ... import _infrastructure
from time_tracker._infrastructure import DB


def delete_time_entry(req: func.HttpRequest) -> func.HttpResponse:
time_entry_dao = _infrastructure.TimeEntriesSQLDao(DB())
time_entry_service = _domain.TimeEntryService(time_entry_dao)
use_case = _domain._use_cases.DeleteTimeEntryUseCase(time_entry_service)

try:
time_entry_id = int(req.route_params.get("id"))
deleted_time_entry = use_case.delete_time_entry(time_entry_id)
if not deleted_time_entry:
return func.HttpResponse(
body="Not found",
status_code=404,
mimetype="application/json"
)

return func.HttpResponse(
body=json.dumps(deleted_time_entry.__dict__, default=str),
status_code=200,
mimetype="application/json",
)

except ValueError:
return func.HttpResponse(
body=b"Invalid Format ID",
status_code=400,
mimetype="application/json"
)
1 change: 1 addition & 0 deletions V2/time_tracker/time_entries/_domain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
from ._services import TimeEntryService
from ._use_cases import (
CreateTimeEntryUseCase,
DeleteTimeEntryUseCase
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ class TimeEntriesDao(abc.ABC):
@abc.abstractmethod
def create(self, time_entry_data: TimeEntry) -> TimeEntry:
pass

@abc.abstractmethod
def delete(self, id: int) -> TimeEntry:
pass
3 changes: 3 additions & 0 deletions V2/time_tracker/time_entries/_domain/_services/_time_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ def __init__(self, time_entry_dao: TimeEntriesDao):

def create(self, time_entry_data: TimeEntry) -> TimeEntry:
return self.time_entry_dao.create(time_entry_data)

def delete(self, time_entry_id: int) -> TimeEntry:
return self.time_entry_dao.delete(time_entry_id)
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# flake8: noqa
from ._create_time_entry_use_case import CreateTimeEntryUseCase
from ._delete_time_entry_use_case import DeleteTimeEntryUseCase
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from time_tracker.time_entries._domain import TimeEntry, TimeEntryService


class DeleteTimeEntryUseCase:

def __init__(self, time_entry_service: TimeEntryService):
self.time_entry_service = time_entry_service

def delete_time_entry(self, id: int) -> TimeEntry:
return self.time_entry_service.delete(id)
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ def create(self, time_entry_data: domain.TimeEntry) -> domain.TimeEntry:
def __create_time_entry_dto(self, time_entry: dict) -> domain.TimeEntry:
time_entry = {key: time_entry.get(key) for key in self.time_entry_key}
return domain.TimeEntry(**time_entry)

def delete(self, time_entry_id: int) -> domain.TimeEntry:
query = (
self.time_entry.update()
.where(self.time_entry.c.id == time_entry_id)
.values({"deleted": True})
)
self.db.get_session().execute(query)
query_deleted_time_entry = sqlalchemy.sql.select(self.time_entry).where(self.time_entry.c.id == time_entry_id)
time_entry = self.db.get_session().execute(query_deleted_time_entry).one_or_none()
return self.__create_time_entry_dto(dict(time_entry)) if time_entry else None
3 changes: 2 additions & 1 deletion V2/time_tracker/time_entries/interface.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# flake8: noqa
from ._application import create_time_entry
from ._application import create_time_entry
from ._application import delete_time_entry