diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 963b6d4731..3614f7e1d5 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -3,6 +3,7 @@ from django.conf import settings from django.contrib.postgres.search import TrigramSimilarity +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import ( Case, @@ -78,7 +79,7 @@ NewDischargeReasonEnum, ) from care.facility.models.patient_consultation import PatientConsultation -from care.users.models import User +from care.users.models import GENDER_CHOICES, User from care.utils.cache.cache_allowed_facilities import get_accessible_facilities from care.utils.filters.choicefilter import CareChoiceFilter from care.utils.filters.multiselect import MultiSelectFilter @@ -108,14 +109,22 @@ class PatientFilterSet(filters.FilterSet): phone_number = filters.CharFilter(field_name="phone_number") emergency_phone_number = filters.CharFilter(field_name="emergency_phone_number") allow_transfer = filters.BooleanFilter(field_name="allow_transfer") - name = filters.CharFilter(field_name="name", lookup_expr="icontains") + name = filters.CharFilter( + field_name="name", lookup_expr="icontains", max_length=200 + ) patient_no = filters.CharFilter( - field_name=f"{last_consultation_field}__patient_no", lookup_expr="iexact" + field_name=f"{last_consultation_field}__patient_no", + lookup_expr="iexact", + max_length=100, + ) + gender = filters.ChoiceFilter(field_name="gender", choices=GENDER_CHOICES) + age = filters.NumberFilter(field_name="age", validators=[MinValueValidator(0)]) + age_min = filters.NumberFilter( + field_name="age", lookup_expr="gte", validators=[MinValueValidator(0)] + ) + age_max = filters.NumberFilter( + field_name="age", lookup_expr="lte", validators=[MinValueValidator(0)] ) - gender = filters.NumberFilter(field_name="gender") - age = filters.NumberFilter(field_name="age") - age_min = filters.NumberFilter(field_name="age", lookup_expr="gte") - age_max = filters.NumberFilter(field_name="age", lookup_expr="lte") deprecated_covid_category = filters.ChoiceFilter( field_name=f"{last_consultation_field}__deprecated_covid_category", choices=COVID_CATEGORY_CHOICES, @@ -142,7 +151,7 @@ def filter_by_category(self, queryset, name, value): created_date = filters.DateFromToRangeFilter(field_name="created_date") modified_date = filters.DateFromToRangeFilter(field_name="modified_date") - srf_id = filters.CharFilter(field_name="srf_id") + srf_id = filters.CharFilter(field_name="srf_id", max_length=200) is_declared_positive = filters.BooleanFilter(field_name="is_declared_positive") date_declared_positive = filters.DateFromToRangeFilter( field_name="date_declared_positive" @@ -160,14 +169,16 @@ def filter_by_category(self, queryset, name, value): # Location Based Filtering district = filters.NumberFilter(field_name="district__id") district_name = filters.CharFilter( - field_name="district__name", lookup_expr="icontains" + field_name="district__name", lookup_expr="icontains", max_length=255 ) local_body = filters.NumberFilter(field_name="local_body__id") local_body_name = filters.CharFilter( - field_name="local_body__name", lookup_expr="icontains" + field_name="local_body__name", lookup_expr="icontains", max_length=255 ) state = filters.NumberFilter(field_name="state__id") - state_name = filters.CharFilter(field_name="state__name", lookup_expr="icontains") + state_name = filters.CharFilter( + field_name="state__name", lookup_expr="icontains", max_length=255 + ) # Consultation Fields is_kasp = filters.BooleanFilter(field_name=f"{last_consultation_field}__is_kasp") last_consultation_kasp_enabled_date = filters.DateFromToRangeFilter( @@ -226,9 +237,12 @@ def filter_by_bed_type(self, queryset, name, value): ) # Vaccination Filters - covin_id = filters.CharFilter(field_name="covin_id") + covin_id = filters.CharFilter(field_name="covin_id", max_length=15) is_vaccinated = filters.BooleanFilter(field_name="is_vaccinated") - number_of_doses = filters.NumberFilter(field_name="number_of_doses") + number_of_doses = filters.NumberFilter( + field_name="number_of_doses", + validators=[MinValueValidator(0), MaxValueValidator(3)], + ) # Permission Filters assigned_to = filters.NumberFilter(field_name="assigned_to") # Other Filters diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 37accc7c2c..6046435ddb 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -728,6 +728,135 @@ def test_filter_by_has_consents(self): self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertEqual(res.json()["count"], 3) + def test_filter_by_invalid_params(self): + self.client.force_authenticate(user=self.user) + + # name length > 200 words + invalid_name_param = "a" * 201 + res = self.client.get(self.get_base_url() + f"?name={invalid_name_param}") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value has at most 200 characters (it has 201).", + res.json()["name"], + ) + + # invalid gender choice + invalid_gender = 4 + res = self.client.get(self.get_base_url() + f"?gender={invalid_gender}") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Select a valid choice. 4 is not one of the available choices.", + res.json()["gender"], + ) + + # invalid value for age, age max , age min filter (i.e <0) + invalid_age = -2 + res = self.client.get(self.get_base_url() + f"?age={invalid_age}") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value is greater than or equal to 0.", res.json()["age"] + ) + + invalid_min_age = -2 + res = self.client.get(self.get_base_url() + f"?age_min={invalid_min_age}") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value is greater than or equal to 0.", res.json()["age_min"] + ) + + invalid_max_age = -2 + res = self.client.get(self.get_base_url() + f"?age_max={invalid_max_age}") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value is greater than or equal to 0.", res.json()["age_max"] + ) + + # invalid number_of_doses param >3 or <0 + invalid_number_of_doses = -2 + res = self.client.get( + self.get_base_url() + f"?number_of_doses={invalid_number_of_doses}" + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value is greater than or equal to 0.", + res.json()["number_of_doses"], + ) + + invalid_number_of_doses = 4 + res = self.client.get( + self.get_base_url() + f"?number_of_doses={invalid_number_of_doses}" + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value is less than or equal to 3.", + res.json()["number_of_doses"], + ) + + # invalid srf id length > 200 words + invalid_srf_param = "a" * 201 + res = self.client.get(self.get_base_url() + f"?srf_id={invalid_srf_param}") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value has at most 200 characters (it has 201).", + res.json()["srf_id"], + ) + + # invalid district_name length > 255 words + invalid_district_name_param = "a" * 256 + res = self.client.get( + self.get_base_url() + f"?district_name={invalid_district_name_param}" + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value has at most 255 characters (it has 256).", + res.json()["district_name"], + ) + + # invalid local_body_name length > 255 words + invalid_local_body_name_param = "a" * 256 + res = self.client.get( + self.get_base_url() + f"?local_body_name={invalid_local_body_name_param}" + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value has at most 255 characters (it has 256).", + res.json()["local_body_name"], + ) + + # invalid state_name length > 255 words + invalid_state_name_param = "a" * 256 + res = self.client.get( + self.get_base_url() + f"?state_name={invalid_state_name_param}" + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value has at most 255 characters (it has 256).", + res.json()["state_name"], + ) + + # invalid patient no value > 100 + invalid_patient_no_param = "A" * 101 + res = self.client.get( + self.get_base_url() + f"?patient_no={invalid_patient_no_param}" + ) + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value has at most 100 characters (it has 101).", + res.json()["patient_no"], + ) + + def test_invalid_covin_id_param(self): + self.client.force_authenticate(user=self.user) + + # Test invalid covin_id length > 15 characters + invalid_covin_id = "A" * 16 + res = self.client.get(self.get_base_url() + f"?covin_id={invalid_covin_id}") + self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn( + "Ensure this value has at most 15 characters (it has 16).", + res.json()["covin_id"], + ) + class DischargePatientFilterTestCase(TestUtils, APITestCase): @classmethod