Skip to content

Commit 3cddc6b

Browse files
authored
fix: TT-270 Fix handle exceptions in create update entries (#301)
1 parent fa30f57 commit 3cddc6b

File tree

7 files changed

+230
-8
lines changed

7 files changed

+230
-8
lines changed

tests/conftest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import copy
22
from datetime import datetime, timedelta
3+
from http import HTTPStatus
34

45
import jwt
56
import pytest
@@ -217,13 +218,22 @@ def time_entries_dao():
217218
return time_entries_dao
218219

219220

220-
@pytest.fixture(scope="module")
221+
@pytest.fixture
221222
def running_time_entry(
222223
time_entry_repository: TimeEntryCosmosDBRepository,
223224
owner_id: str,
224225
tenant_id: str,
225226
event_context: EventContext,
227+
mocker,
226228
):
229+
mocker.patch(
230+
'time_tracker_api.time_entries.time_entries_repository.are_related_entry_entities_valid',
231+
return_value={
232+
"is_valid": True,
233+
"status_code": HTTPStatus.OK,
234+
"message": "Related entry entities valid",
235+
},
236+
)
227237
current_time_entry_repository = copy.copy(time_entry_repository)
228238
created_time_entry = current_time_entry_repository.create(
229239
{

tests/time_tracker_api/time_entries/time_entries_model_test.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
from http import HTTPStatus
12
from unittest.mock import Mock, patch
23
import pytest
34
from faker import Faker
45

6+
from commons.data_access_layer.cosmos_db import CustomError
57
from commons.data_access_layer.database import EventContext
6-
from time_tracker_api.time_entries.time_entries_model import (
7-
TimeEntryCosmosDBModel,
8-
)
98
from time_tracker_api.time_entries.time_entries_repository import (
109
TimeEntryCosmosDBRepository,
1110
TimeEntryCosmosDBModel,
@@ -17,6 +16,7 @@ def create_time_entry(
1716
end_date: str,
1817
owner_id: str,
1918
tenant_id: str,
19+
mocker,
2020
event_context: EventContext,
2121
time_entry_repository: TimeEntryCosmosDBRepository,
2222
) -> TimeEntryCosmosDBModel:
@@ -30,6 +30,15 @@ def create_time_entry(
3030
"tenant_id": tenant_id,
3131
}
3232

33+
mocker.patch(
34+
'time_tracker_api.time_entries.time_entries_repository.are_related_entry_entities_valid',
35+
return_value={
36+
"is_valid": True,
37+
"status_code": HTTPStatus.OK,
38+
"message": "Related entry entities valid",
39+
},
40+
)
41+
3342
created_item = time_entry_repository.create(
3443
data, event_context, mapper=TimeEntryCosmosDBModel
3544
)
@@ -78,6 +87,7 @@ def test_find_interception_with_date_range_should_find(
7887
end_date_: str,
7988
owner_id: str,
8089
tenant_id: str,
90+
mocker,
8191
time_entry_repository: TimeEntryCosmosDBRepository,
8292
event_context: EventContext,
8393
):
@@ -86,6 +96,7 @@ def test_find_interception_with_date_range_should_find(
8696
end_date,
8797
owner_id,
8898
tenant_id,
99+
mocker,
89100
event_context,
90101
time_entry_repository,
91102
)
@@ -142,12 +153,14 @@ def test_find_interception_with_date_range_should_not_find(
142153
tenant_id: str,
143154
time_entry_repository: TimeEntryCosmosDBRepository,
144155
event_context: EventContext,
156+
mocker,
145157
):
146158
existing_item = create_time_entry(
147159
start_date,
148160
end_date,
149161
owner_id,
150162
tenant_id,
163+
mocker,
151164
event_context,
152165
time_entry_repository,
153166
)
@@ -171,14 +184,17 @@ def test_find_interception_should_ignore_id_of_existing_item(
171184
tenant_id: str,
172185
time_entry_repository: TimeEntryCosmosDBRepository,
173186
event_context: EventContext,
187+
mocker,
174188
):
175189
start_date = "2020-10-01T05:00:00.000Z"
176190
end_date = "2020-10-01T10:00:00.000Z"
191+
177192
existing_item = create_time_entry(
178193
start_date,
179194
end_date,
180195
owner_id,
181196
tenant_id,
197+
mocker,
182198
event_context,
183199
time_entry_repository,
184200
)
@@ -229,10 +245,10 @@ def test_find_running_should_not_find_any_item(
229245
owner_id: str,
230246
time_entry_repository: TimeEntryCosmosDBRepository,
231247
):
232-
try:
248+
with pytest.raises(CustomError) as custom_error:
233249
time_entry_repository.find_running(tenant_id, owner_id)
234-
except Exception as e:
235-
assert type(e) is StopIteration
250+
251+
assert custom_error.value.code == HTTPStatus.NO_CONTENT
236252

237253

238254
@patch(

tests/time_tracker_api/time_entries/time_entries_namespace_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,15 @@ def test_create_with_valid_uuid_format_should_return_created(
749749
valid_uuid: str,
750750
time_entries_dao,
751751
):
752+
mocker.patch(
753+
'time_tracker_api.time_entries.time_entries_repository.are_related_entry_entities_valid',
754+
return_value={
755+
"is_valid": True,
756+
"status_code": HTTPStatus.OK,
757+
"message": "Related entry entities valid",
758+
},
759+
)
760+
752761
repository_container_create_item_mock = mocker.patch.object(
753762
time_entries_dao.repository.container,
754763
'create_item',

tests/utils/validate_entries_test.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from http import HTTPStatus
2+
3+
from azure.cosmos.exceptions import CosmosResourceNotFoundError
4+
from faker import Faker
5+
6+
from time_tracker_api.activities import activities_model
7+
from time_tracker_api.activities.activities_model import ActivityCosmosDBDao
8+
from time_tracker_api.projects.projects_model import ProjectCosmosDBDao
9+
from utils.validate_entries import (
10+
are_related_entry_entities_valid,
11+
exists_related_entity,
12+
)
13+
14+
fake = Faker()
15+
16+
17+
def test_validate_related_entry_entities_must_failed_if_project_id_is_empty():
18+
are_entities_valid = are_related_entry_entities_valid(
19+
project_id=None, activity_id=fake.uuid4()
20+
)
21+
22+
assert are_entities_valid.get('is_valid') is False
23+
assert are_entities_valid.get('status_code') == HTTPStatus.BAD_REQUEST
24+
assert are_entities_valid.get('message') == "Project id can not be empty"
25+
26+
27+
def test_validate_related_entry_entities_must_failed_if_activity_id_is_empty():
28+
are_entities_valid = are_related_entry_entities_valid(
29+
project_id=fake.uuid4(), activity_id=None
30+
)
31+
32+
assert are_entities_valid.get('is_valid') is False
33+
assert are_entities_valid.get('status_code') == HTTPStatus.BAD_REQUEST
34+
assert are_entities_valid.get('message') == "Activity id can not be empty"
35+
36+
37+
def test_validate_related_entry_entities_must_pass_if_the_data_is_valid(
38+
mocker,
39+
):
40+
mocker.patch.object(ProjectCosmosDBDao, 'get')
41+
mocker.patch.object(ActivityCosmosDBDao, 'get')
42+
43+
are_entities_valid = are_related_entry_entities_valid(
44+
project_id=fake.uuid4(), activity_id=fake.uuid4()
45+
)
46+
47+
assert are_entities_valid.get('is_valid') is True
48+
assert are_entities_valid.get('status_code') == HTTPStatus.OK
49+
assert are_entities_valid.get('message') == 'Related entry entities valid'
50+
51+
52+
def test_validate_related_entry_entities_must_fail_if_the_project_id_does_not_exists(
53+
mocker,
54+
):
55+
mocker.patch(
56+
'utils.validate_entries.exists_related_entity', return_value=False
57+
)
58+
59+
are_entities_valid = are_related_entry_entities_valid(
60+
project_id=fake.uuid4(), activity_id=fake.uuid4()
61+
)
62+
63+
assert are_entities_valid.get('is_valid') is False
64+
assert are_entities_valid.get('status_code') == HTTPStatus.BAD_REQUEST
65+
assert (
66+
are_entities_valid.get('message') == 'Related Project does not exists'
67+
)
68+
69+
70+
def test_validate_related_entry_entities_must_fail_if_the_activity_id_does_not_exists(
71+
mocker,
72+
):
73+
mocker.patch.object(ProjectCosmosDBDao, 'get')
74+
75+
mocker.patch.object(
76+
ActivityCosmosDBDao, 'get', side_effect=CosmosResourceNotFoundError
77+
)
78+
79+
are_entities_valid = are_related_entry_entities_valid(
80+
project_id=fake.uuid4(), activity_id=fake.uuid4()
81+
)
82+
83+
assert are_entities_valid.get('is_valid') is False
84+
assert are_entities_valid.get('status_code') == HTTPStatus.BAD_REQUEST
85+
assert (
86+
are_entities_valid.get('message') == 'Related Activity does not exists'
87+
)
88+
89+
90+
def test_exists_related_entity_should_return_true_if_entity_exists(mocker):
91+
mocker.patch.object(ActivityCosmosDBDao, 'get')
92+
activity_dao = activities_model.create_dao()
93+
94+
exists_entity = exists_related_entity(
95+
related_id=fake.uuid4(), dao=activity_dao
96+
)
97+
98+
assert exists_entity is True
99+
100+
101+
def test_exists_related_entity_should_return_false_if_entity_does_not_exists(
102+
mocker,
103+
):
104+
mocker.patch.object(
105+
ActivityCosmosDBDao, 'get', side_effect=CosmosResourceNotFoundError
106+
)
107+
activity_dao = activities_model.create_dao()
108+
109+
exists_entity = exists_related_entity(
110+
related_id=fake.uuid4(), dao=activity_dao
111+
)
112+
113+
assert exists_entity is False

time_tracker_api/time_entries/time_entries_namespace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
),
3838
'activity_id': UUID(
3939
title='Activity',
40-
required=False,
40+
required=True,
4141
description='The id of the selected activity',
4242
example=faker.uuid4(),
4343
),

time_tracker_api/time_entries/time_entries_repository.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131
from utils.query_builder import CosmosDBQueryBuilder, Order
3232
from utils.time import str_to_datetime
33+
from utils.validate_entries import are_related_entry_entities_valid
3334

3435

3536
class TimeEntryCosmosDBRepository(CosmosDBRepository):
@@ -303,6 +304,21 @@ def find_running(
303304
def validate_data(self, data, event_context: EventContext):
304305
start_date = data.get('start_date')
305306

307+
related_project_id = data.get('project_id')
308+
related_activity_id = data.get('activity_id')
309+
310+
are_related_entities_valid = are_related_entry_entities_valid(
311+
project_id=related_project_id, activity_id=related_activity_id
312+
)
313+
314+
if not are_related_entities_valid.get('is_valid'):
315+
status_code = are_related_entities_valid.get('status_code')
316+
message = are_related_entities_valid.get('message')
317+
raise CustomError(
318+
status_code,
319+
description=message,
320+
)
321+
306322
if data.get('end_date') is not None:
307323
if data['end_date'] <= start_date:
308324
raise CustomError(

utils/validate_entries.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from http import HTTPStatus
2+
3+
from azure.cosmos.exceptions import CosmosResourceNotFoundError
4+
5+
from time_tracker_api.projects import projects_model
6+
from time_tracker_api.activities import activities_model
7+
8+
9+
def are_related_entry_entities_valid(project_id: str, activity_id: str):
10+
if not project_id:
11+
return {
12+
"is_valid": False,
13+
"status_code": HTTPStatus.BAD_REQUEST,
14+
"message": "Project id can not be empty",
15+
}
16+
17+
if not activity_id:
18+
return {
19+
"is_valid": False,
20+
"status_code": HTTPStatus.BAD_REQUEST,
21+
"message": "Activity id can not be empty",
22+
}
23+
24+
exists_project = exists_related_entity(
25+
project_id, projects_model.create_dao()
26+
)
27+
28+
if not exists_project:
29+
return {
30+
"is_valid": False,
31+
"status_code": HTTPStatus.BAD_REQUEST,
32+
"message": "Related Project does not exists",
33+
}
34+
35+
exists_activity = exists_related_entity(
36+
activity_id, activities_model.create_dao()
37+
)
38+
39+
if not exists_activity:
40+
return {
41+
"is_valid": False,
42+
"status_code": HTTPStatus.BAD_REQUEST,
43+
"message": "Related Activity does not exists",
44+
}
45+
46+
return {
47+
"is_valid": True,
48+
"status_code": HTTPStatus.OK,
49+
"message": "Related entry entities valid",
50+
}
51+
52+
53+
def exists_related_entity(related_id: str, dao):
54+
try:
55+
dao.get(related_id)
56+
return True
57+
except CosmosResourceNotFoundError:
58+
return False

0 commit comments

Comments
 (0)