Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding tests for questionnaire and few minor changes #2745

Merged
merged 9 commits into from
Jan 21, 2025
14 changes: 5 additions & 9 deletions care/emr/api/viewsets/questionnaire.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class QuestionnaireViewSet(EMRModelViewSet):
def permissions_controller(self, request):
if self.action in ["list", "retrieve", "get_organizations"]:
return AuthorizationController.call("can_read_questionnaire", request.user)
if self.action in ["create", "update", "set_organizations", "set_tags"]:
if self.action in ["create", "set_organizations", "set_tags"]:
return AuthorizationController.call("can_write_questionnaire", request.user)

return request.user.is_authenticated
Expand Down Expand Up @@ -147,14 +147,10 @@ def submit(self, request, *args, **kwargs):
raise PermissionDenied(
"Permission Denied to submit patient questionnaire"
)
else:
patient = get_object_or_404(Patient, external_id=request_params.patient)
if not AuthorizationController.call(
"can_submit_questionnaire_patient_obj", request.user, patient
):
raise PermissionDenied(
"Permission Denied to submit patient questionnaire"
)
elif not AuthorizationController.call(
"can_submit_questionnaire_patient_obj", request.user, patient
):
raise PermissionDenied("Permission Denied to submit patient questionnaire")
with transaction.atomic():
response = handle_response(questionnaire, request_params, request.user)
return Response(QuestionnaireResponseReadSpec.serialize(response).to_json())
Expand Down
3 changes: 2 additions & 1 deletion care/emr/resources/patient/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def perform_extra_deserialization(self, is_update, obj):

class PatientListSpec(PatientBaseSpec):
date_of_birth: datetime.date | None = None
age: int | None = None
year_of_birth: datetime.date | None = None

Comment on lines +83 to +84
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Type hint for year_of_birth seems... interesting.

The year_of_birth is defined as datetime.date | None, but based on PatientCreateSpec.perform_extra_deserialization, it's actually stored as an integer year value. Perhaps we should align the type hints with reality?

Apply this diff to fix the type hint:

-    year_of_birth: datetime.date | None = None
+    year_of_birth: int | None = None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
year_of_birth: datetime.date | None = None
year_of_birth: int | None = None

created_date: datetime.datetime
modified_date: datetime.datetime

Expand Down
27 changes: 23 additions & 4 deletions care/emr/resources/questionnaire/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import uuid
from datetime import datetime
from urllib.parse import urlparse

from dateutil import parser
from django.utils import timezone
from rest_framework.exceptions import ValidationError

Expand All @@ -27,7 +27,19 @@ def check_required(questionnaire, questionnaire_ref):
return False


def validate_data(values, value_type, questionnaire_ref):
def get_valid_choices(question):
"""
Extracts valid choices from a choice question dictionary.
"""
answer_options = question.get("answer_option", [])
if not answer_options:
error = f"No 'answer_option' found in question with id {question.get('id')}."
raise ValueError(error)

return [option["value"] for option in answer_options if "value" in option]


def validate_data(values, value_type, questionnaire_ref): # noqa PLR0912
"""
Validate the type of the value based on the question type.
Args:
Expand All @@ -51,11 +63,18 @@ def validate_data(values, value_type, questionnaire_ref):
if value.value.lower() not in ["true", "false", "1", "0"]:
errors.append(f"Invalid boolean value: {value.value}")
elif value_type == QuestionType.date.value:
parser.parse(value.value).date()
datetime.strptime(value.value, "%Y-%m-%d").date() # noqa DTZ007
elif value_type == QuestionType.datetime.value:
parser.parse(value.value)
datetime.strptime(value.value, "%Y-%m-%dT%H:%M:%S") # noqa DTZ007
elif value_type == QuestionType.time.value:
datetime.strptime(value.value, "%H:%M:%S") # noqa DTZ007
elif value_type == QuestionType.choice.value:
if value.value not in get_valid_choices(questionnaire_ref):
errors.append(f"Invalid {value_type}")
elif value_type == QuestionType.url.value:
parsed = urlparse(value.value)
if not all([parsed.scheme, parsed.netloc]):
errors.append(f"Invalid {value_type}")
except ValueError:
errors.append(f"Invalid {value_type}")
except Exception:
Expand Down
52 changes: 52 additions & 0 deletions care/emr/tests/test_diagnosis_api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid
from secrets import choice
from unittest.mock import patch

Expand Down Expand Up @@ -344,6 +345,57 @@ def test_create_diagnosis_with_permissions_and_no_association_with_facility(self
response = self.client.post(self.base_url, diagnosis_data_dict, format="json")
self.assertEqual(response.status_code, 403)

def test_create_diagnosis_with_permissions_with_mismatched_patient_id(self):
"""
Users with `can_write_encounter` on a encounter with different patient => (HTTP 400).
"""
permissions = [EncounterPermissions.can_write_encounter.name]
role = self.create_role_with_permissions(permissions)
self.attach_role_facility_organization_user(self.organization, self.user, role)

encounter = self.create_encounter(
patient=self.create_patient(),
facility=self.facility,
organization=self.organization,
status=None,
)
diagnosis_data_dict = self.generate_data_for_diagnosis(encounter)

response = self.client.post(self.base_url, diagnosis_data_dict, format="json")
response_data = response.json()
self.assertEqual(response.status_code, 400)
self.assertIn("errors", response_data)
error = response_data["errors"][0]
self.assertEqual(error["type"], "validation_error")
self.assertIn(
"Patient external ID mismatch with encounter's patient", error["msg"]
)

def test_create_diagnosis_with_permissions_with_invalid_encounter_id(self):
"""
Users with `can_write_encounter` on a incomplete encounter => (HTTP 400).
"""
permissions = [EncounterPermissions.can_write_encounter.name]
role = self.create_role_with_permissions(permissions)
self.attach_role_facility_organization_user(self.organization, self.user, role)

encounter = self.create_encounter(
patient=self.create_patient(),
facility=self.facility,
organization=self.organization,
status=None,
)
diagnosis_data_dict = self.generate_data_for_diagnosis(encounter)
diagnosis_data_dict["encounter"] = uuid.uuid4()

response = self.client.post(self.base_url, diagnosis_data_dict, format="json")
response_data = response.json()
self.assertEqual(response.status_code, 400)
self.assertIn("errors", response_data)
error = response_data["errors"][0]
self.assertEqual(error["type"], "value_error")
self.assertIn("Encounter not found", error["msg"])

# RETRIEVE TESTS
def test_retrieve_diagnosis_with_permissions(self):
"""
Expand Down
Loading
Loading