diff --git a/care/facility/api/serializers/asset.py b/care/facility/api/serializers/asset.py index 62692a02bd..88ad84fa52 100644 --- a/care/facility/api/serializers/asset.py +++ b/care/facility/api/serializers/asset.py @@ -3,7 +3,8 @@ from django.core.cache import cache from django.db import models, transaction from django.db.models import F, Value -from django.db.models.functions import Cast, Coalesce, NullIf +from django.db.models.fields.json import KT +from django.db.models.functions import Coalesce, NullIf from django.shortcuts import get_object_or_404 from django.utils.timezone import now from drf_spectacular.utils import extend_schema_field @@ -214,8 +215,8 @@ def validate(self, attrs): raise ValidationError({"asset_class": "Cannot change asset class"}) if meta := attrs.get("meta"): - current_location = attrs.get( - "current_location", self.instance.current_location + current_location = ( + attrs.get("current_location") or self.instance.current_location ) ip_address = meta.get("local_ip_address") middleware_hostname = ( @@ -227,12 +228,7 @@ def validate(self, attrs): asset_using_ip = ( Asset.objects.annotate( resolved_middleware_hostname=Coalesce( - NullIf( - Cast( - F("meta__middleware_hostname"), models.CharField() - ), - Value('""'), - ), + NullIf(KT("meta__middleware_hostname"), Value("")), NullIf( F("current_location__middleware_address"), Value("") ), diff --git a/care/facility/api/viewsets/asset.py b/care/facility/api/viewsets/asset.py index b3ae295a56..fbc53f817d 100644 --- a/care/facility/api/viewsets/asset.py +++ b/care/facility/api/viewsets/asset.py @@ -3,7 +3,8 @@ from django.conf import settings from django.core.cache import cache from django.db.models import CharField, Exists, F, OuterRef, Q, Subquery, Value -from django.db.models.functions import Cast, Coalesce, NullIf +from django.db.models.fields.json import KT +from django.db.models.functions import Coalesce, NullIf from django.db.models.signals import post_save from django.dispatch import receiver from django.http import Http404 @@ -464,10 +465,7 @@ def list(self, request, *args, **kwargs): ) .annotate( resolved_middleware_hostname=Coalesce( - NullIf( - Cast(F("meta__middleware_hostname"), CharField()), - Value('""'), - ), + NullIf(KT("meta__middleware_hostname"), Value("")), NullIf(F("current_location__middleware_address"), Value("")), F("current_location__facility__middleware_address"), output_field=CharField(), diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index e329a597df..0a18f48909 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -301,6 +301,7 @@ def filter_queryset(self, request, queryset, view): q_filters = Q(facility__id__in=allowed_facilities) if view.action == "retrieve": q_filters |= Q(consultations__facility__id__in=allowed_facilities) + queryset = queryset.distinct("id") q_filters |= Q(last_consultation__assigned_to=request.user) q_filters |= Q(assigned_to=request.user) queryset = queryset.filter(q_filters) @@ -340,7 +341,7 @@ def filter_queryset(self, request, queryset, view): ) ).order_by(ordering) - return queryset.distinct(ordering.lstrip("-") if ordering else "id") + return queryset @extend_schema_view(history=extend_schema(tags=["patient"])) diff --git a/care/facility/tests/test_asset_api.py b/care/facility/tests/test_asset_api.py index 7bbb458e92..48d934a0f7 100644 --- a/care/facility/tests/test_asset_api.py +++ b/care/facility/tests/test_asset_api.py @@ -3,6 +3,7 @@ from rest_framework.test import APITestCase from care.facility.models import Asset, Bed +from care.utils.assetintegration.asset_classes import AssetClasses from care.utils.tests.test_utils import TestUtils @@ -31,7 +32,6 @@ def test_list_assets(self): def test_create_asset(self): sample_data = { "name": "Test Asset", - "current_location": self.asset_location.pk, "asset_type": 50, "location": self.asset_location.external_id, } @@ -41,7 +41,6 @@ def test_create_asset(self): def test_create_asset_with_warranty_past(self): sample_data = { "name": "Test Asset", - "current_location": self.asset_location.pk, "asset_type": 50, "location": self.asset_location.external_id, "warranty_amc_end_of_validity": "2000-04-01", @@ -57,7 +56,6 @@ def test_retrieve_asset(self): def test_update_asset(self): sample_data = { "name": "Updated Test Asset", - "current_location": self.asset_location.pk, "asset_type": 50, "location": self.asset_location.external_id, } @@ -166,3 +164,182 @@ def test_asset_filter_warranty_amc_end_of_validity(self): self.assertNotIn( str(asset1.external_id), [asset["id"] for asset in response.data["results"]] ) + + +class AssetConfigValidationTestCase(TestUtils, APITestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.state = cls.create_state() + cls.district = cls.create_district(cls.state) + cls.local_body = cls.create_local_body(cls.district) + cls.super_user = cls.create_super_user("su", cls.district) + cls.hostname = "test-middleware.com" + cls.facility = cls.create_facility( + cls.super_user, + cls.district, + cls.local_body, + middleware_address=cls.hostname, + ) + cls.asset_location = cls.create_asset_location(cls.facility) + cls.user = cls.create_user("staff", cls.district, home_facility=cls.facility) + + def test_create_asset_with_unique_ip(self): + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": self.asset_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": {"local_ip_address": "192.168.1.14"}, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_create_asset_with_duplicate_ip(self): + self.create_asset( + self.asset_location, + name="I was here first", + asset_class=AssetClasses.HL7MONITOR.name, + meta={"local_ip_address": "192.168.1.14"}, + ) + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": self.asset_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": {"local_ip_address": "192.168.1.14"}, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("I was here first", response.json()["non_field_errors"][0]) + + def test_create_asset_with_duplicate_ip_same_hostname_on_location(self): + test_location = self.create_asset_location( + self.facility, middleware_address=self.hostname + ) + self.create_asset( + test_location, + name="I was here first", + asset_class=AssetClasses.HL7MONITOR.name, + meta={"local_ip_address": "192.168.1.14"}, + ) + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": test_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": {"local_ip_address": "192.168.1.14"}, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("I was here first", response.json()["non_field_errors"][0]) + + def test_create_asset_with_duplicate_ip_same_hostname_on_asset(self): + self.create_asset( + self.asset_location, + name="I was here first", + asset_class=AssetClasses.HL7MONITOR.name, + meta={ + "local_ip_address": "192.168.1.14", + "middleware_hostname": self.hostname, + }, + ) + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": self.asset_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": {"local_ip_address": "192.168.1.14"}, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("I was here first", response.json()["non_field_errors"][0]) + + def test_create_asset_with_duplicate_ip_same_hostname_on_location_asset(self): + test_location = self.create_asset_location( + self.facility, middleware_address=self.hostname + ) + self.create_asset( + test_location, + name="I was here first", + asset_class=AssetClasses.HL7MONITOR.name, + meta={ + "local_ip_address": "192.168.1.14", + "middleware_hostname": self.hostname, + }, + ) + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": test_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": {"local_ip_address": "192.168.1.14"}, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("I was here first", response.json()["non_field_errors"][0]) + + def test_create_asset_with_duplicate_ip_different_hostname_on_location(self): + test_location = self.create_asset_location( + self.facility, middleware_address="not-test-middleware.com" + ) + self.create_asset( + self.asset_location, + name="I was here first", + asset_class=AssetClasses.HL7MONITOR.name, + meta={"local_ip_address": "192.168.1.14"}, + ) + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": test_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": {"local_ip_address": "192.168.1.14"}, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_create_asset_with_duplicate_ip_different_hostname_on_asset(self): + self.create_asset( + self.asset_location, + name="I was here first", + asset_class=AssetClasses.HL7MONITOR.name, + meta={"local_ip_address": "192.168.1.14"}, + ) + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": self.asset_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": { + "local_ip_address": "192.168.1.14", + "middleware_hostname": "not-test-middleware.com", + }, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_create_asset_with_duplicate_ip_different_hostname_on_location_asset(self): + test_location = self.create_asset_location( + self.facility, middleware_address="not-test-middleware.com" + ) + self.create_asset( + test_location, + name="I was here first", + asset_class=AssetClasses.HL7MONITOR.name, + meta={ + "local_ip_address": "192.168.1.14", + "middleware_hostname": self.hostname, + }, + ) + sample_data = { + "name": "Test Asset", + "asset_type": 50, + "location": test_location.external_id, + "asset_class": AssetClasses.HL7MONITOR.name, + "meta": { + "local_ip_address": "192.168.1.14", + "middleware_hostname": "not-test-middleware.com", + }, + } + response = self.client.post("/api/v1/asset/", sample_data, format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) diff --git a/care/facility/tests/test_middleware_config.py b/care/facility/tests/test_middleware_config.py new file mode 100644 index 0000000000..26305c6261 --- /dev/null +++ b/care/facility/tests/test_middleware_config.py @@ -0,0 +1,139 @@ +from rest_framework import status +from rest_framework.test import APITestCase + +from care.utils.assetintegration.asset_classes import AssetClasses +from care.utils.tests.test_utils import TestUtils +from config.authentication import MiddlewareUser + + +class MiddlewareConfigTestCase(TestUtils, APITestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.state = cls.create_state() + cls.district = cls.create_district(cls.state) + cls.local_body = cls.create_local_body(cls.district) + cls.super_user = cls.create_super_user("su", cls.district) + cls.hostname = "test-middleware.com" + cls.facility = cls.create_facility( + cls.super_user, + cls.district, + cls.local_body, + middleware_address=cls.hostname, + ) + cls.middleware_user = MiddlewareUser(facility=cls.facility) + + def setUp(self) -> None: + self.client.force_authenticate(user=self.middleware_user) + + def test_fetch_middleware_config_hostname_on_facility(self): + test_location = self.create_asset_location(self.facility) + test_asset = self.create_asset( + test_location, + asset_class=AssetClasses.HL7MONITOR.name, + meta={"local_ip_address": "192.168.1.14"}, + ) + response = self.client.get( + f"/api/v1/asset_config/?middleware_hostname={self.hostname}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["id"], str(test_asset.external_id)) + + def test_fetch_middleware_config_hostname_on_facility_location(self): + test_location = self.create_asset_location( + self.facility, middleware_address=self.hostname + ) + test_asset = self.create_asset( + test_location, + asset_class=AssetClasses.HL7MONITOR.name, + meta={"local_ip_address": "192.168.1.14"}, + ) + response = self.client.get( + f"/api/v1/asset_config/?middleware_hostname={self.facility.middleware_address}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["id"], str(test_asset.external_id)) + + def test_fetch_middleware_config_hostname_on_facility_asset(self): + test_location = self.create_asset_location(self.facility) + test_asset = self.create_asset( + test_location, + asset_class=AssetClasses.HL7MONITOR.name, + meta={ + "local_ip_address": "192.168.1.14", + "middleware_hostname": self.hostname, + }, + ) + response = self.client.get( + f"/api/v1/asset_config/?middleware_hostname={self.facility.middleware_address}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["id"], str(test_asset.external_id)) + + def test_fetch_middleware_config_hostname_on_facility_location_asset(self): + test_location = self.create_asset_location( + self.facility, middleware_address=self.hostname + ) + test_asset = self.create_asset( + test_location, + asset_class=AssetClasses.HL7MONITOR.name, + meta={ + "local_ip_address": "192.168.1.14", + "middleware_hostname": self.hostname, + }, + ) + response = self.client.get( + f"/api/v1/asset_config/?middleware_hostname={self.facility.middleware_address}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["id"], str(test_asset.external_id)) + + def test_fetch_middleware_config_different_hostname_on_location_same_on_asset(self): + test_location = self.create_asset_location( + self.facility, middleware_address="not-test-middleware.com" + ) + test_asset = self.create_asset( + test_location, + asset_class=AssetClasses.HL7MONITOR.name, + meta={ + "local_ip_address": "192.168.1.14", + "middleware_hostname": self.hostname, + }, + ) + response = self.client.get( + f"/api/v1/asset_config/?middleware_hostname={self.facility.middleware_address}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["id"], str(test_asset.external_id)) + + def test_fetch_middleware_config_different_hostname_on_location(self): + test_location = self.create_asset_location( + self.facility, middleware_address="not-test-middleware.com" + ) + self.create_asset( + test_location, + asset_class=AssetClasses.HL7MONITOR.name, + meta={"local_ip_address": "192.168.1.14"}, + ) + response = self.client.get( + f"/api/v1/asset_config/?middleware_hostname={self.facility.middleware_address}" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, []) + + def test_fetch_middleware_config_different_hostname_on_asset_same_on_location(self): + test_location = self.create_asset_location( + self.facility, middleware_address=self.hostname + ) + test_asset = self.create_asset( + test_location, + asset_class=AssetClasses.HL7MONITOR.name, + meta={ + "local_ip_address": "192.168.1.14", + "middleware_hostname": "not-test-middleware.com", + }, + ) + response = self.client.get( + "/api/v1/asset_config/?middleware_hostname=not-test-middleware.com" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["id"], str(test_asset.external_id)) diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py index 6a34c198ca..a6c2307312 100644 --- a/care/utils/tests/test_utils.py +++ b/care/utils/tests/test_utils.py @@ -385,7 +385,6 @@ def create_asset_location(cls, facility: Facility, **kwargs) -> AssetLocation: "name": "asset1 location", "location_type": 1, "facility": facility, - "middleware_address": "example.com", } data.update(kwargs) return AssetLocation.objects.create(**data)