Skip to content

Commit 86867c9

Browse files
authored
refactor: TT-245 refactor on find all function in cosmos db repository class (#312)
* refactor: TT-245 Refactor function find all inside cosmosdb Repository * refactor: TT-245 remove unnecesary functions and add some testing
1 parent 23a35e7 commit 86867c9

File tree

8 files changed

+151
-210
lines changed

8 files changed

+151
-210
lines changed

commons/data_access_layer/cosmos_db.py

Lines changed: 25 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import dataclasses
22
import logging
3-
from typing import Callable, List
3+
from typing import Callable
44

55
import azure.cosmos.cosmos_client as cosmos_client
66
import azure.cosmos.exceptions as exceptions
7-
import flask
87
from azure.cosmos import ContainerProxy, PartitionKey
98
from flask import Flask
109
from werkzeug.exceptions import HTTPException
1110

1211
from commons.data_access_layer.database import CRUDDao, EventContext
12+
from utils.query_builder import CosmosDBQueryBuilder
1313

1414

1515
class CosmosDBFacade:
@@ -124,55 +124,6 @@ def from_definition(
124124
custom_cosmos_helper=custom_cosmos_helper,
125125
)
126126

127-
@staticmethod
128-
def create_sql_condition_for_visibility(
129-
visible_only: bool, container_name='c'
130-
) -> str:
131-
if visible_only:
132-
# We are considering that `deleted == null` is not a choice
133-
return 'AND NOT IS_DEFINED(%s.deleted)' % container_name
134-
return ''
135-
136-
@staticmethod
137-
def create_sql_active_condition(
138-
status_value: str, container_name='c'
139-
) -> str:
140-
if status_value != None:
141-
not_defined_condition = ''
142-
condition_operand = ' AND '
143-
if status_value == 'active':
144-
not_defined_condition = (
145-
'AND NOT IS_DEFINED({container_name}.status)'.format(
146-
container_name=container_name
147-
)
148-
)
149-
condition_operand = ' OR '
150-
151-
defined_condition = '(IS_DEFINED({container_name}.status) \
152-
AND {container_name}.status = \'{status_value}\')'.format(
153-
container_name=container_name, status_value=status_value
154-
)
155-
return (
156-
not_defined_condition + condition_operand + defined_condition
157-
)
158-
159-
return ''
160-
161-
@staticmethod
162-
def create_sql_where_conditions(
163-
conditions: dict, container_name='c'
164-
) -> str:
165-
where_conditions = []
166-
for k in conditions.keys():
167-
where_conditions.append(f'{container_name}.{k} = @{k}')
168-
169-
if len(where_conditions) > 0:
170-
return "AND {where_conditions_clause}".format(
171-
where_conditions_clause=" AND ".join(where_conditions)
172-
)
173-
else:
174-
return ""
175-
176127
@staticmethod
177128
def generate_params(conditions: dict) -> list:
178129
result = []
@@ -206,16 +157,6 @@ def attach_context(data: dict, event_context: EventContext):
206157
"session_id": event_context.session_id,
207158
}
208159

209-
@staticmethod
210-
def create_sql_date_range_filter(date_range: dict) -> str:
211-
if 'start_date' in date_range and 'end_date' in date_range:
212-
return """
213-
AND ((c.start_date BETWEEN @start_date AND @end_date) OR
214-
(c.end_date BETWEEN @start_date AND @end_date))
215-
"""
216-
else:
217-
return ''
218-
219160
def create(
220161
self, data: dict, event_context: EventContext, mapper: Callable = None
221162
):
@@ -257,53 +198,38 @@ def find_all(
257198
mapper: Callable = None,
258199
):
259200
conditions = conditions if conditions else {}
260-
partition_key_value = self.find_partition_key_value(event_context)
261-
max_count = self.get_page_size_or(max_count)
262-
params = [
263-
{"name": "@partition_key_value", "value": partition_key_value},
264-
{"name": "@offset", "value": offset},
265-
{"name": "@max_count", "value": max_count},
266-
]
267-
268-
status_value = None
269-
if conditions.get('status') != None:
270-
status_value = conditions.get('status')
201+
max_count: int = self.get_page_size_or(max_count)
202+
203+
status_value = conditions.get('status')
204+
if status_value:
271205
conditions.pop('status')
272206

273207
date_range = date_range if date_range else {}
274-
date_range_params = (
275-
self.generate_params(date_range) if date_range else []
276-
)
277-
params.extend(self.generate_params(conditions))
278-
params.extend(date_range_params)
279-
280-
query_str = """
281-
SELECT * FROM c
282-
WHERE c.{partition_key_attribute}=@partition_key_value
283-
{conditions_clause}
284-
{active_condition}
285-
{date_range_sql_condition}
286-
{visibility_condition}
287-
{order_clause}
288-
OFFSET @offset LIMIT @max_count
289-
""".format(
290-
partition_key_attribute=self.partition_key_attribute,
291-
visibility_condition=self.create_sql_condition_for_visibility(
292-
visible_only
293-
),
294-
active_condition=self.create_sql_active_condition(status_value),
295-
conditions_clause=self.create_sql_where_conditions(conditions),
296-
date_range_sql_condition=self.create_sql_date_range_filter(
297-
date_range
298-
),
299-
order_clause=self.create_sql_order_clause(),
208+
209+
query_builder = (
210+
CosmosDBQueryBuilder()
211+
.add_sql_where_equal_condition(conditions)
212+
.add_sql_active_condition(status_value)
213+
.add_sql_date_range_condition(date_range)
214+
.add_sql_visibility_condition(visible_only)
215+
.add_sql_limit_condition(max_count)
216+
.add_sql_offset_condition(offset)
217+
.build()
300218
)
301219

220+
if len(self.order_fields) > 1:
221+
attribute = self.order_fields[0]
222+
order = self.order_fields[1]
223+
query_builder.add_sql_order_by_condition(attribute, order)
224+
225+
query_str = query_builder.get_query()
226+
params = query_builder.get_parameters()
227+
partition_key_value = self.find_partition_key_value(event_context)
228+
302229
result = self.container.query_items(
303230
query=query_str,
304231
parameters=params,
305232
partition_key=partition_key_value,
306-
max_item_count=max_count,
307233
)
308234

309235
function_mapper = self.get_mapper_or_dict(mapper)

tests/commons/data_access_layer/cosmos_db_test.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -660,28 +660,6 @@ def test_delete_permanently_with_valid_id_should_succeed(
660660
assert e.status_code == 404
661661

662662

663-
def test_repository_create_sql_where_conditions_with_multiple_values(
664-
cosmos_db_repository: CosmosDBRepository,
665-
):
666-
result = cosmos_db_repository.create_sql_where_conditions(
667-
{'owner_id': 'mark', 'customer_id': 'me'}, "c"
668-
)
669-
670-
assert result is not None
671-
assert (
672-
result == "AND c.owner_id = @owner_id AND c.customer_id = @customer_id"
673-
)
674-
675-
676-
def test_repository_create_sql_where_conditions_with_no_values(
677-
cosmos_db_repository: CosmosDBRepository,
678-
):
679-
result = cosmos_db_repository.create_sql_where_conditions({}, "c")
680-
681-
assert result is not None
682-
assert result == ""
683-
684-
685663
def test_repository_append_conditions_values(
686664
cosmos_db_repository: CosmosDBRepository,
687665
):

tests/time_tracker_api/activities/activities_model_test.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from unittest.mock import Mock, patch
2-
import pytest
32

43
from commons.data_access_layer.database import EventContext
54
from time_tracker_api.activities.activities_model import (
@@ -8,9 +7,6 @@
87
)
98

109

11-
@patch(
12-
'time_tracker_api.activities.activities_model.ActivityCosmosDBRepository.create_sql_condition_for_visibility'
13-
)
1410
@patch(
1511
'time_tracker_api.activities.activities_model.ActivityCosmosDBRepository.find_partition_key_value'
1612
)

tests/time_tracker_api/time_entries/time_entries_namespace_test.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,8 @@
88
from pytest_mock import MockFixture, pytest
99

1010
from utils.time import (
11-
get_current_year,
12-
get_current_month,
1311
current_datetime,
1412
current_datetime_str,
15-
get_date_range_of_month,
16-
datetime_str,
1713
)
1814
from utils import worked_time
1915
from time_tracker_api.time_entries.time_entries_model import (
@@ -204,10 +200,6 @@ def test_get_time_entry_should_succeed_with_valid_id(
204200
'time_tracker_api.time_entries.time_entries_dao.TimeEntriesCosmosDBDao.handle_date_filter_args',
205201
Mock(),
206202
)
207-
@patch(
208-
'time_tracker_api.time_entries.time_entries_repository.TimeEntryCosmosDBRepository.create_sql_date_range_filter',
209-
Mock(),
210-
)
211203
@patch(
212204
'commons.data_access_layer.cosmos_db.CosmosDBRepository.generate_params',
213205
Mock(),
@@ -232,7 +224,6 @@ def test_get_time_entries_by_type_of_user_when_is_user_tester(
232224
expected_user_ids,
233225
):
234226
test_user_id = "id1"
235-
non_test_user_id = "id2"
236227
te1 = TimeEntryCosmosDBModel(
237228
{
238229
"id": '1',
@@ -285,10 +276,6 @@ def test_get_time_entries_by_type_of_user_when_is_user_tester(
285276
'time_tracker_api.time_entries.time_entries_dao.TimeEntriesCosmosDBDao.handle_date_filter_args',
286277
Mock(),
287278
)
288-
@patch(
289-
'time_tracker_api.time_entries.time_entries_repository.TimeEntryCosmosDBRepository.create_sql_date_range_filter',
290-
Mock(),
291-
)
292279
@patch(
293280
'commons.data_access_layer.cosmos_db.CosmosDBRepository.generate_params',
294281
Mock(),
@@ -313,7 +300,6 @@ def test_get_time_entries_by_type_of_user_when_is_not_user_tester(
313300
expected_user_ids,
314301
):
315302
test_user_id = "id1"
316-
non_test_user_id = "id2"
317303
te1 = TimeEntryCosmosDBModel(
318304
{
319305
"id": '1',
@@ -386,7 +372,6 @@ def test_get_time_entry_should_succeed_with_valid_id(
386372
)
387373
def test_get_time_entry_raise_http_exception(
388374
client: FlaskClient,
389-
mocker: MockFixture,
390375
valid_header: dict,
391376
valid_id: str,
392377
http_exception: HTTPException,
@@ -407,7 +392,6 @@ def test_get_time_entry_raise_http_exception(
407392

408393
def test_update_time_entry_calls_partial_update_with_incoming_payload(
409394
client: FlaskClient,
410-
mocker: MockFixture,
411395
valid_header: dict,
412396
valid_id: str,
413397
owner_id: str,
@@ -465,7 +449,6 @@ def test_update_time_entry_should_reject_bad_request(
465449

466450
def test_update_time_entry_raise_not_found(
467451
client: FlaskClient,
468-
mocker: MockFixture,
469452
valid_header: dict,
470453
valid_id: str,
471454
owner_id: str,
@@ -499,7 +482,6 @@ def test_update_time_entry_raise_not_found(
499482

500483
def test_delete_time_entry_calls_delete(
501484
client: FlaskClient,
502-
mocker: MockFixture,
503485
valid_header: dict,
504486
valid_id: str,
505487
time_entries_dao,
@@ -529,7 +511,6 @@ def test_delete_time_entry_calls_delete(
529511
)
530512
def test_delete_time_entry_raise_http_exception(
531513
client: FlaskClient,
532-
mocker: MockFixture,
533514
valid_header: dict,
534515
valid_id: str,
535516
http_exception: HTTPException,
@@ -554,7 +535,6 @@ def test_delete_time_entry_raise_http_exception(
554535

555536
def test_stop_time_entry_calls_partial_update(
556537
client: FlaskClient,
557-
mocker: MockFixture,
558538
valid_header: dict,
559539
valid_id: str,
560540
time_entries_dao,
@@ -581,7 +561,6 @@ def test_stop_time_entry_calls_partial_update(
581561

582562
def test_stop_time_entry_raise_unprocessable_entity(
583563
client: FlaskClient,
584-
mocker: MockFixture,
585564
valid_header: dict,
586565
valid_id: str,
587566
time_entries_dao,
@@ -611,7 +590,6 @@ def test_stop_time_entry_raise_unprocessable_entity(
611590

612591
def test_restart_time_entry_calls_partial_update(
613592
client: FlaskClient,
614-
mocker: MockFixture,
615593
valid_header: dict,
616594
valid_id: str,
617595
time_entries_dao,
@@ -638,7 +616,6 @@ def test_restart_time_entry_calls_partial_update(
638616

639617
def test_restart_time_entry_raise_unprocessable_entity(
640618
client: FlaskClient,
641-
mocker: MockFixture,
642619
valid_header: dict,
643620
valid_id: str,
644621
time_entries_dao,

tests/time_tracker_api/time_entries/time_entries_query_builder_test.py

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from utils.repository import remove_white_spaces
77

88

9-
def test_TimeEntryQueryBuilder_is_subclass_CosmosDBQueryBuilder():
9+
def test_time_entry_query_builder_should_be_subclass_of_cosmos_query_builder():
1010
query_builder = CosmosDBQueryBuilder()
1111
time_entries_query_builder = TimeEntryQueryBuilder()
1212

@@ -15,50 +15,6 @@ def test_TimeEntryQueryBuilder_is_subclass_CosmosDBQueryBuilder():
1515
)
1616

1717

18-
def test_add_sql_date_range_condition_should_update_where_list():
19-
start_date = "2021-03-19T05:07:00.000Z"
20-
end_date = "2021-03-25T10:00:00.000Z"
21-
time_entry_query_builder = (
22-
TimeEntryQueryBuilder().add_sql_date_range_condition(
23-
{
24-
"start_date": start_date,
25-
"end_date": end_date,
26-
}
27-
)
28-
)
29-
expected_params = [
30-
{"name": "@start_date", "value": start_date},
31-
{"name": "@end_date", "value": end_date},
32-
]
33-
assert len(time_entry_query_builder.where_conditions) == 1
34-
assert len(time_entry_query_builder.parameters) == len(expected_params)
35-
assert time_entry_query_builder.get_parameters() == expected_params
36-
37-
38-
def test_build_with_add_sql_date_range_condition():
39-
time_entry_query_builder = (
40-
TimeEntryQueryBuilder()
41-
.add_sql_date_range_condition(
42-
{
43-
"start_date": "2021-04-19T05:00:00.000Z",
44-
"end_date": "2021-04-20T10:00:00.000Z",
45-
}
46-
)
47-
.build()
48-
)
49-
50-
expected_query = """
51-
SELECT * FROM c
52-
WHERE ((c.start_date BETWEEN @start_date AND @end_date) OR
53-
(c.end_date BETWEEN @start_date AND @end_date))
54-
"""
55-
query = time_entry_query_builder.get_query()
56-
57-
assert remove_white_spaces(query) == remove_white_spaces(expected_query)
58-
assert len(time_entry_query_builder.where_conditions) == 1
59-
assert len(time_entry_query_builder.get_parameters()) == 2
60-
61-
6218
def test_add_sql_interception_with_date_range_condition():
6319
start_date = "2021-01-19T05:07:00.000Z"
6420
end_date = "2021-01-25T10:00:00.000Z"

0 commit comments

Comments
 (0)