Skip to content

Commit 7dc262f

Browse files
author
EliuX
committed
feat: Close #107 Allow empty char values to be converted to null
1 parent 32a80b4 commit 7dc262f

File tree

5 files changed

+83
-3
lines changed

5 files changed

+83
-3
lines changed

commons/data_access_layer/cosmos_db.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,14 @@ def check_visibility(item, throw_not_found_if_deleted):
132132
if throw_not_found_if_deleted and item.get('deleted') is not None:
133133
raise exceptions.CosmosResourceNotFoundError(message='Deleted item',
134134
status_code=404)
135-
136135
return item
137136

137+
@staticmethod
138+
def replace_empty_value_per_none(item_data: dict) -> dict:
139+
for k, v in item_data.items():
140+
if isinstance(v, str) and len(v) == 0:
141+
item_data[k] = None
142+
138143
def create(self, data: dict, mapper: Callable = None):
139144
self.on_create(data)
140145
function_mapper = self.get_mapper_or_dict(mapper)
@@ -207,6 +212,8 @@ def on_create(self, new_item_data: dict):
207212
if new_item_data.get('id') is None:
208213
new_item_data['id'] = generate_uuid4()
209214

215+
self.replace_empty_value_per_none(new_item_data)
216+
210217
def on_update(self, update_item_data: dict):
211218
pass
212219

tests/commons/data_access_layer/cosmos_db_test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,3 +602,28 @@ def test_datetime_str_comparison():
602602

603603
assert now_str > datetime_str(now - timedelta(days=1))
604604
assert now_str < datetime_str(now + timedelta(days=1))
605+
606+
607+
def test_replace_empty_value_per_none(tenant_id: str):
608+
initial_value = dict(id=fake.uuid4(),
609+
name=fake.name(),
610+
empty_str_attrib="",
611+
array_attrib=[1, 2, 3],
612+
empty_array_attrib=[],
613+
description=" ",
614+
age=fake.pyint(min_value=10, max_value=80),
615+
size=0,
616+
tenant_id=tenant_id)
617+
618+
input = initial_value.copy()
619+
620+
CosmosDBRepository.replace_empty_value_per_none(input)
621+
622+
assert input["name"] == initial_value["name"]
623+
assert input["empty_str_attrib"] is None
624+
assert input["array_attrib"] == initial_value["array_attrib"]
625+
assert input["empty_array_attrib"] == initial_value["empty_array_attrib"]
626+
assert input["description"] == initial_value["description"]
627+
assert input["age"] == initial_value["age"]
628+
assert input["size"] == initial_value["size"]
629+
assert input["tenant_id"] == initial_value["tenant_id"]

tests/time_tracker_api/time_entries/time_entries_namespace_test.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from flask import json
66
from flask.testing import FlaskClient
77
from flask_restplus._http import HTTPStatus
8-
from pytest_mock import MockFixture
8+
from pytest_mock import MockFixture, pytest
99

1010
from commons.data_access_layer.cosmos_db import current_datetime, current_datetime_str
1111

@@ -423,3 +423,50 @@ def test_get_running_should_return_not_found_if_find_running_throws_StopIteratio
423423
assert HTTPStatus.NOT_FOUND == response.status_code
424424
repository_update_mock.assert_called_once_with(partition_key_value=tenant_id,
425425
owner_id=owner_id)
426+
427+
@pytest.mark.parametrize(
428+
'invalid_uuid', ["zxy", "zxy%s" % fake.uuid4(), "%szxy" % fake.uuid4(), " "]
429+
)
430+
def test_create_with_invalid_uuid_format_should_return_bad_request(client: FlaskClient,
431+
mocker: MockFixture,
432+
valid_header: dict,
433+
invalid_uuid: str):
434+
from time_tracker_api.time_entries.time_entries_namespace import time_entries_dao
435+
repository_container_create_item_mock = mocker.patch.object(time_entries_dao.repository.container,
436+
'create_item',
437+
return_value=fake_time_entry)
438+
invalid_time_entry_input = {
439+
"project_id": fake.uuid4(),
440+
"activity_id": invalid_uuid,
441+
}
442+
response = client.post("/time-entries",
443+
headers=valid_header,
444+
json=invalid_time_entry_input,
445+
follow_redirects=True)
446+
447+
assert HTTPStatus.BAD_REQUEST == response.status_code
448+
repository_container_create_item_mock.assert_not_called()
449+
450+
@pytest.mark.parametrize(
451+
'valid_uuid', ["", fake.uuid4()]
452+
)
453+
def test_create_with_valid_uuid_format_should_return_created(client: FlaskClient,
454+
mocker: MockFixture,
455+
valid_header: dict,
456+
valid_uuid: str):
457+
from time_tracker_api.time_entries.time_entries_namespace import time_entries_dao
458+
repository_container_create_item_mock = mocker.patch.object(time_entries_dao.repository.container,
459+
'create_item',
460+
return_value=fake_time_entry)
461+
invalid_time_entry_input = {
462+
"project_id": fake.uuid4(),
463+
"activity_id": valid_uuid,
464+
}
465+
response = client.post("/time-entries",
466+
headers=valid_header,
467+
json=invalid_time_entry_input,
468+
follow_redirects=True)
469+
470+
assert HTTPStatus.CREATED == response.status_code
471+
repository_container_create_item_mock.assert_called()
472+

time_tracker_api/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class NullableString(fields.String):
4646
class UUID(NullableString):
4747
def __init__(self, *args, **kwargs):
4848
super(UUID, self).__init__(*args, **kwargs)
49-
self.pattern = UUID_REGEX
49+
self.pattern = r"^(|%s)$" % UUID_REGEX
5050

5151

5252
common_fields = {

time_tracker_api/time_entries/time_entries_model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def on_create(self, new_item_data: dict):
8585
def on_update(self, updated_item_data: dict):
8686
CosmosDBRepository.on_update(self, updated_item_data)
8787
self.validate_data(updated_item_data)
88+
self.replace_empty_value_per_none(updated_item_data)
8889

8990
def find_interception_with_date_range(self, start_date, end_date, owner_id, partition_key_value,
9091
ignore_id=None, visible_only=True, mapper: Callable = None):

0 commit comments

Comments
 (0)