Skip to content

Commit

Permalink
Adding tests for questionnaire and few minor changes (#2745)
Browse files Browse the repository at this point in the history
Questionnaire Tests
  • Loading branch information
DraKen0009 authored Jan 21, 2025
1 parent d3c263a commit 35577b2
Show file tree
Hide file tree
Showing 10 changed files with 995 additions and 14 deletions.
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
18 changes: 18 additions & 0 deletions care/emr/migrations/0009_medicationrequest_authored_on.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2025-01-21 11:15

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('emr', '0008_medicationrequest_medication'),
]

operations = [
migrations.AddField(
model_name='medicationrequest',
name='authored_on',
field=models.DateTimeField(blank=True, default=None, null=True),
),
]
1 change: 1 addition & 0 deletions care/emr/models/medication_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ class MedicationRequest(EMRBaseModel):
encounter = models.ForeignKey("emr.Encounter", on_delete=models.CASCADE)
dosage_instruction = models.JSONField(default=list, null=True, blank=True)
note = models.TextField(null=True, blank=True)
authored_on = models.DateTimeField(null=True, blank=True, default=None)
1 change: 1 addition & 0 deletions care/emr/resources/medication/request/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ class BaseMedicationRequestSpec(MedicationRequestResource):
encounter: UUID4

dosage_instruction: list[DosageInstruction] = Field()
authored_on: datetime

note: str | None = Field(None)

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

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

0 comments on commit 35577b2

Please sign in to comment.