Skip to content

Commit 500a5d0

Browse files
EdansRocksAndrés Soto
andauthored
feat: TT-366 V2 - PUT update activity (#331)
* feat: TT-366 Add update activities method * refactor: TT-366 Solving SonalCloud code smells Co-authored-by: Andrés Soto <[email protected]>
1 parent abec3f4 commit 500a5d0

File tree

16 files changed

+218
-44
lines changed

16 files changed

+218
-44
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ node_modules
2929
# Serverless directories
3030
.serverless/
3131

32-
# Azure Functions json config
33-
host.json
34-
local.settings.json
35-
3632
# Files generated for development
3733
.env
3834
timetracker-api-postman-collection.json

V2/serverless.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,14 @@ functions:
5555
methods:
5656
- DELETE
5757
route: activities/{id}
58-
authLevel: anonymous
58+
authLevel: anonymous
59+
60+
update_activity:
61+
handler: time_entries/interface.update_activity
62+
events:
63+
- http: true
64+
x-azure-settings:
65+
methods:
66+
- PUT
67+
route: activities/{id}
68+
authLevel: anonymous
Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
from time_entries._application._activities import (
2-
_get_activities,
3-
_delete_activity,
4-
)
1+
from time_entries._application import _activities as activities
2+
from faker import Faker
3+
54
import azure.functions as func
65
import json
76
import typing
87

98

9+
ACTIVITY_URL = '/api/activities/'
10+
11+
1012
def test__activity_azure_endpoint__returns_all_activities(
1113
create_temp_activities,
1214
):
1315
activities_json, tmp_directory = create_temp_activities
14-
_get_activities.JSON_PATH = tmp_directory
15-
req = func.HttpRequest(method='GET', body=None, url='/api/activities')
16+
activities._get_activities.JSON_PATH = tmp_directory
17+
req = func.HttpRequest(method='GET', body=None, url=ACTIVITY_URL)
1618

17-
response = _get_activities.get_activities(req)
19+
response = activities.get_activities(req)
1820
activities_json_data = response.get_body().decode("utf-8")
1921

2022
assert response.status_code == 200
@@ -25,15 +27,15 @@ def test__activity_azure_endpoint__returns_an_activity__when_activity_matches_it
2527
create_temp_activities,
2628
):
2729
activities_json, tmp_directory = create_temp_activities
28-
_get_activities.JSON_PATH = tmp_directory
30+
activities._get_activities.JSON_PATH = tmp_directory
2931
req = func.HttpRequest(
3032
method='GET',
3133
body=None,
32-
url='/api/activities/',
34+
url=ACTIVITY_URL,
3335
route_params={"id": activities_json[0]['id']},
3436
)
3537

36-
response = _get_activities.get_activities(req)
38+
response = activities.get_activities(req)
3739
activitiy_json_data = response.get_body().decode("utf-8")
3840

3941
assert response.status_code == 200
@@ -44,16 +46,37 @@ def test__activity_azure_endpoint__returns_an_activity_with_inactive_status__whe
4446
create_temp_activities,
4547
):
4648
activities_json, tmp_directory = create_temp_activities
47-
_delete_activity.JSON_PATH = tmp_directory
49+
activities._delete_activity.JSON_PATH = tmp_directory
4850
req = func.HttpRequest(
4951
method='DELETE',
5052
body=None,
51-
url='/api/activities/',
53+
url=ACTIVITY_URL,
5254
route_params={"id": activities_json[0]['id']},
5355
)
5456

55-
response = _delete_activity.delete_activity(req)
57+
response = activities.delete_activity(req)
5658
activity_json_data = json.loads(response.get_body().decode("utf-8"))
5759

5860
assert response.status_code == 200
5961
assert activity_json_data['status'] == 'inactive'
62+
63+
64+
def test__update_activity_azure_endpoint__returns_an_activity__when_found_an_activity_to_update(
65+
create_temp_activities,
66+
):
67+
activities_json, tmp_directory = create_temp_activities
68+
activities._update_activity.JSON_PATH = tmp_directory
69+
activity_data = {"description": Faker().sentence()}
70+
req = func.HttpRequest(
71+
method='PUT',
72+
body=json.dumps(activity_data).encode("utf-8"),
73+
url=ACTIVITY_URL,
74+
route_params={"id": activities_json[0]['id']},
75+
)
76+
77+
response = activities.update_activity(req)
78+
activitiy_json_data = response.get_body().decode("utf-8")
79+
new_activity = {**activities_json[0], **activity_data}
80+
81+
assert response.status_code == 200
82+
assert activitiy_json_data == json.dumps(new_activity)

V2/tests/integration/daos/activities_json_dao_test.py

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@
66
import typing
77

88

9+
fake_activities = [
10+
{
11+
'id': Faker().uuid4(),
12+
'name': Faker().user_name(),
13+
'description': Faker().sentence(),
14+
'deleted': Faker().uuid4(),
15+
'status': 'active',
16+
'tenant_id': Faker().uuid4(),
17+
}
18+
]
19+
20+
921
@pytest.fixture(name='create_fake_activities')
1022
def _create_fake_activities(mocker) -> typing.List[Activity]:
1123
def _creator(activities):
@@ -20,18 +32,7 @@ def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matche
2032
create_fake_activities,
2133
):
2234
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
23-
activities = create_fake_activities(
24-
[
25-
{
26-
"name": "test_name",
27-
"description": "test_description",
28-
"tenant_id": "test_tenant_id",
29-
"id": "test_id",
30-
"deleted": "test_deleted",
31-
"status": "test_status",
32-
}
33-
]
34-
)
35+
activities = create_fake_activities(fake_activities)
3536
activity_dto = activities.pop()
3637

3738
result = activities_json_dao.get_by_id(activity_dto.id)
@@ -55,19 +56,7 @@ def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_acti
5556
):
5657
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
5758
number_of_activities = 3
58-
activities = create_fake_activities(
59-
[
60-
{
61-
"name": "test_name",
62-
"description": "test_description",
63-
"tenant_id": "test_tenant_id",
64-
"id": "test_id",
65-
"deleted": "test_deleted",
66-
"status": "test_status",
67-
}
68-
]
69-
* number_of_activities
70-
)
59+
activities = create_fake_activities(fake_activities * number_of_activities)
7160

7261
result = activities_json_dao.get_all()
7362

@@ -117,3 +106,29 @@ def test_delete__returns_none__when_no_activity_matching_its_id_is_found(
117106
result = activities_json_dao.delete(Faker().uuid4())
118107

119108
assert result is None
109+
110+
111+
def test_update__returns_an_activity_dto__when_found_one_activity_to_update(
112+
create_fake_activities,
113+
):
114+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
115+
activities = create_fake_activities(fake_activities)
116+
activity_dto = activities.pop()
117+
activity_data = {"description": Faker().sentence()}
118+
119+
result = activities_json_dao.update(activity_dto.id, activity_data)
120+
new_activity = {**activity_dto.__dict__, **activity_data}
121+
122+
assert result == Activity(**new_activity)
123+
124+
125+
def test_update__returns_none__when_doesnt_found_one_activity_to_update(
126+
create_fake_activities,
127+
):
128+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
129+
create_fake_activities([])
130+
activity_data = {"description": Faker().sentence()}
131+
132+
result = activities_json_dao.update('', activity_data)
133+
134+
assert result == None

V2/tests/unit/services/activity_service_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,20 @@ def test__delete_activity__uses_the_activity_dao__to_change_activity_status(
4141

4242
assert activity_dao.delete.called
4343
assert expected_activity == deleted_activity
44+
45+
46+
def test__update_activity__uses_the_activity_dao__to_update_one_activity(
47+
mocker,
48+
):
49+
expected_activity = mocker.Mock()
50+
activity_dao = mocker.Mock(
51+
update=mocker.Mock(return_value=expected_activity)
52+
)
53+
activity_service = ActivityService(activity_dao)
54+
55+
updated_activity = activity_service.update(
56+
Faker().uuid4(), Faker().pydict()
57+
)
58+
59+
assert activity_dao.update.called
60+
assert expected_activity == updated_activity

V2/tests/unit/use_cases/activities_use_case_test.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,20 @@ def test__delete_activity_function__uses_the_activity_service__to_change_activit
4949

5050
assert activity_service.delete.called
5151
assert expected_activity == deleted_activity
52+
53+
54+
def test__update_activity_function__uses_the_activities_service__to_update_an_activity(
55+
mocker: MockFixture,
56+
):
57+
expected_activity = mocker.Mock()
58+
activity_service = mocker.Mock(
59+
update=mocker.Mock(return_value=expected_activity)
60+
)
61+
62+
activity_use_case = _use_cases.UpdateActivityUseCase(activity_service)
63+
updated_activity = activity_use_case.update_activity(
64+
fake.uuid4(), fake.pydict()
65+
)
66+
67+
assert activity_service.update.called
68+
assert expected_activity == updated_activity
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from ._activities import get_activities
22
from ._activities import delete_activity
3+
from ._activities import update_activity
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from ._get_activities import get_activities
22
from ._delete_activity import delete_activity
3+
from ._update_activity import update_activity
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from time_entries._infrastructure import ActivitiesJsonDao
2+
from time_entries._domain import ActivityService, Activity, _use_cases
3+
4+
import azure.functions as func
5+
import dataclasses
6+
import json
7+
import logging
8+
9+
JSON_PATH = (
10+
'time_entries/_infrastructure/_data_persistence/activities_data.json'
11+
)
12+
13+
14+
def update_activity(req: func.HttpRequest) -> func.HttpResponse:
15+
logging.info(
16+
'Python HTTP trigger function processed a request to update an activity.'
17+
)
18+
activity_id = req.route_params.get('id')
19+
activity_data = req.get_json() if req.get_body() else {}
20+
activity_keys = [field.name for field in dataclasses.fields(Activity)]
21+
22+
if all(key in activity_keys for key in activity_data.keys()):
23+
response = _update(activity_id, activity_data)
24+
status_code = 200
25+
else:
26+
response = b'Incorrect activity body'
27+
status_code = 400
28+
29+
return func.HttpResponse(
30+
body=response, status_code=status_code, mimetype="application/json"
31+
)
32+
33+
34+
def _update(activity_id: str, activity_data: dict) -> str:
35+
activity_use_case = _use_cases.UpdateActivityUseCase(
36+
_create_activity_service(JSON_PATH)
37+
)
38+
activity = activity_use_case.update_activity(activity_id, activity_data)
39+
return json.dumps(activity.__dict__) if activity else b'Not Found'
40+
41+
42+
def _create_activity_service(path: str):
43+
activity_json = ActivitiesJsonDao(path)
44+
return ActivityService(activity_json)

V2/time_entries/_domain/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
from ._entities import Activity
22
from ._persistence_contracts import ActivitiesDao
33
from ._services import ActivityService
4-
from ._use_cases import GetActivitiesUseCase, GetActivityUseCase
4+
from ._use_cases import (
5+
GetActivitiesUseCase,
6+
GetActivityUseCase,
7+
UpdateActivityUseCase,
8+
)

0 commit comments

Comments
 (0)