Skip to content

Commit 328ad43

Browse files
authored
feat: TT-353 Create V2 Activities DAO (#320)
* feat: TT-353 Create V2 Activities DAO * refactor: TT-353 Solving code smells from SonarCloud * refactor: TT-353 Solving duplicated literal * refactor: TT-353 Add type of argument and return type to functions * refactor: TT-353 Solving comments from PR * refactor: TT-353 Solving Sonarcloud code smell * refactor: TT-353 Changing variable names and tests * refactor: TT-353 Solving requested changes on PR * refactor: TT-353 Solving typo errors on names * refactor: TT-353 Solving requested changes on PR Co-authored-by: Andrés Soto
1 parent 36ccc65 commit 328ad43

File tree

7 files changed

+259
-0
lines changed

7 files changed

+259
-0
lines changed

V2/source/activities_data.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
[
2+
{
3+
"name": "Development",
4+
"description": "Development",
5+
"tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05",
6+
"id": "c61a4a49-3364-49a3-a7f7-0c5f2d15072b",
7+
"_rid": "QUwFAPuumiRhAAAAAAAAAA==",
8+
"_self": "dbs/QUwFAA==/colls/QUwFAPuumiQ=/docs/QUwFAPuumiRhAAAAAAAAAA==/",
9+
"_etag": "\"4e006cc9-0000-0500-0000-607dcc0d0000\"",
10+
"_attachments": "attachments/",
11+
"_last_event_ctx": {
12+
"user_id": "dd76e5d6-3949-46fd-b418-f15bf7c354fa",
13+
"tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05",
14+
"action": "delete",
15+
"description": null,
16+
"container_id": "activity",
17+
"session_id": null
18+
},
19+
"deleted": "b4327ba6-9f96-49ee-a9ac-3c1edf525172",
20+
"status": null,
21+
"_ts": 1618856973
22+
},
23+
{
24+
"name": "Management",
25+
"description": null,
26+
"tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05",
27+
"id": "94ec92e2-a500-4700-a9f6-e41eb7b5507c",
28+
"_last_event_ctx": {
29+
"user_id": "dd76e5d6-3949-46fd-b418-f15bf7c354fa",
30+
"tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05",
31+
"action": "delete",
32+
"description": null,
33+
"container_id": "activity",
34+
"session_id": null
35+
},
36+
"_rid": "QUwFAPuumiRfAAAAAAAAAA==",
37+
"_self": "dbs/QUwFAA==/colls/QUwFAPuumiQ=/docs/QUwFAPuumiRfAAAAAAAAAA==/",
38+
"_etag": "\"4e0069c9-0000-0500-0000-607dcc0d0000\"",
39+
"_attachments": "attachments/",
40+
"deleted": "7cf6efe5-a221-4fe4-b94f-8945127a489a",
41+
"status": null,
42+
"_ts": 1618856973
43+
},
44+
{
45+
"name": "Operations",
46+
"description": "Operation activities performed.",
47+
"tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05",
48+
"id": "d45c770a-b1a0-4bd8-a713-22c01a23e41b",
49+
"_rid": "QUwFAPuumiRjAAAAAAAAAA==",
50+
"_self": "dbs/QUwFAA==/colls/QUwFAPuumiQ=/docs/QUwFAPuumiRjAAAAAAAAAA==/",
51+
"_etag": "\"09009a4d-0000-0500-0000-614b66fb0000\"",
52+
"_attachments": "attachments/",
53+
"_last_event_ctx": {
54+
"user_id": "82ed0f65-051c-4898-890f-870805900e21",
55+
"tenant_id": "cc925a5d-9644-4a4f-8d99-0bee49aadd05",
56+
"action": "update",
57+
"description": null,
58+
"container_id": "activity",
59+
"session_id": null
60+
},
61+
"deleted": "7cf6efe5-a221-4fe4-b94f-8945127a489a",
62+
"status": "active",
63+
"_ts": 1632331515
64+
}
65+
]
66+

V2/source/daos/activities_dao.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from V2.source.dtos.activity import Activity
2+
import abc
3+
import typing
4+
5+
6+
class ActivitiesDao(abc.ABC):
7+
@abc.abstractmethod
8+
def get_by_id(self, id: str) -> Activity:
9+
pass
10+
11+
@abc.abstractmethod
12+
def get_all(self) -> typing.List[Activity]:
13+
pass

V2/source/daos/activities_json_dao.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from V2.source.daos.activities_dao import ActivitiesDao
2+
from V2.source.dtos.activity import Activity
3+
import dataclasses
4+
import json
5+
import typing
6+
7+
8+
class ActivitiesJsonDao(ActivitiesDao):
9+
def __init__(self, json_data_file_path: str):
10+
self.json_data_file_path = json_data_file_path
11+
self.activity_keys = [
12+
field.name for field in dataclasses.fields(Activity)
13+
]
14+
15+
def get_by_id(self, activity_id: str) -> Activity:
16+
activity = {
17+
activity.get('id'): activity
18+
for activity in self.__get_activities_from_file()
19+
}.get(activity_id)
20+
21+
return self.__create_activity_dto(activity) if activity else None
22+
23+
def get_all(self) -> typing.List[Activity]:
24+
return [
25+
self.__create_activity_dto(activity)
26+
for activity in self.__get_activities_from_file()
27+
]
28+
29+
def __get_activities_from_file(self) -> typing.List[dict]:
30+
try:
31+
file = open(self.json_data_file_path)
32+
activities = json.load(file)
33+
file.close()
34+
35+
return activities
36+
37+
except FileNotFoundError:
38+
return []
39+
40+
def __create_activity_dto(self, activity: dict) -> Activity:
41+
activity = {key: activity.get(key) for key in self.activity_keys}
42+
return Activity(**activity)

V2/source/dtos/activity.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass(frozen=True)
5+
class Activity:
6+
id: str
7+
name: str
8+
description: str
9+
deleted: str
10+
status: str
11+
tenant_id: str
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from V2.source.daos.activities_dao import ActivitiesDao
2+
from V2.source.dtos.activity import Activity
3+
import typing
4+
5+
6+
class ActivityService:
7+
def __init__(self, activities_dao: ActivitiesDao):
8+
self.activities_dao = activities_dao
9+
10+
def get_by_id(self, activity_id: str) -> Activity:
11+
return self.activities_dao.get_by_id(activity_id)
12+
13+
def get_all(self) -> typing.List[Activity]:
14+
return self.activities_dao.get_all()
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from V2.source.daos.activities_json_dao import ActivitiesJsonDao
2+
from V2.source.dtos.activity import Activity
3+
from faker import Faker
4+
import json
5+
import pytest
6+
import typing
7+
8+
9+
@pytest.fixture(name='create_fake_activities')
10+
def _create_fake_activities(mocker) -> typing.List[Activity]:
11+
def _creator(activities):
12+
read_data = json.dumps(activities)
13+
mocker.patch('builtins.open', mocker.mock_open(read_data=read_data))
14+
return [Activity(**activity) for activity in activities]
15+
16+
return _creator
17+
18+
19+
def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matches_its_id(
20+
create_fake_activities,
21+
):
22+
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+
activity_dto = activities.pop()
36+
37+
result = activities_json_dao.get_by_id(activity_dto.id)
38+
39+
assert result == activity_dto
40+
41+
42+
def test__get_by_id__returns_none__when_no_activity_matches_its_id(
43+
create_fake_activities,
44+
):
45+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
46+
create_fake_activities([])
47+
48+
result = activities_json_dao.get_by_id(Faker().uuid4())
49+
50+
assert result == None
51+
52+
53+
def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_activities_are_found(
54+
create_fake_activities,
55+
):
56+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
57+
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+
)
71+
72+
result = activities_json_dao.get_all()
73+
74+
assert result == activities
75+
76+
77+
def test_get_all__returns_an_empty_list__when_doesnt_found_any_activities(
78+
create_fake_activities,
79+
):
80+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
81+
activities = create_fake_activities([])
82+
83+
result = activities_json_dao.get_all()
84+
85+
assert result == activities
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from V2.source.services.activity_service import ActivityService
2+
from faker import Faker
3+
4+
5+
def test__get_all__uses_the_activity_dao__to_retrieve_activities(mocker):
6+
expected_activities = mocker.Mock()
7+
activity_dao = mocker.Mock(
8+
get_all=mocker.Mock(return_value=expected_activities)
9+
)
10+
activity_service = ActivityService(activity_dao)
11+
12+
actual_activities = activity_service.get_all()
13+
14+
assert activity_dao.get_all.called
15+
assert expected_activities == actual_activities
16+
17+
18+
def test__get_by_id__uses_the_activity_dao__to_retrieve_one_activity(mocker):
19+
expected_activity = mocker.Mock()
20+
activity_dao = mocker.Mock(
21+
get_by_id=mocker.Mock(return_value=expected_activity)
22+
)
23+
activity_service = ActivityService(activity_dao)
24+
25+
actual_activity = activity_service.get_by_id(Faker().uuid4())
26+
27+
assert activity_dao.get_by_id.called
28+
assert expected_activity == actual_activity

0 commit comments

Comments
 (0)