From c68f64b69c7e7fb10d2722f0fdb0a90973b7e5cd Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:05:55 +0530 Subject: [PATCH 01/16] Rename Coronasafe to OHC --- care/facility/api/serializers/patient_otp.py | 2 +- care/templates/base.html | 2 +- care/templates/email/user_reset_password.html | 2 +- care/templates/email/user_reset_password.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/care/facility/api/serializers/patient_otp.py b/care/facility/api/serializers/patient_otp.py index 9d6951e883..7457ac8454 100644 --- a/care/facility/api/serializers/patient_otp.py +++ b/care/facility/api/serializers/patient_otp.py @@ -26,7 +26,7 @@ def send_sms(otp, phone_number): sendSMS( phone_number, ( - f"CoronaSafe Network Patient Management System Login, OTP is {otp} . " + f"Open Healthcare Network Patient Management System Login, OTP is {otp} . " "Please do not share this Confidential Login Token with anyone else" ), ) diff --git a/care/templates/base.html b/care/templates/base.html index 9270456239..53b13042d1 100644 --- a/care/templates/base.html +++ b/care/templates/base.html @@ -104,7 +104,7 @@ alt="Digital Public Goods logo" /> - CoronaSafe Network is an open-source digital public good designed by + Open Healthcare Network is an open-source digital public good designed by a multi-disciplinary team of innovators and volunteers who are working on a model to support Government efforts.  (Github) diff --git a/care/templates/email/user_reset_password.html b/care/templates/email/user_reset_password.html index 7c5e29a087..8eb0100c40 100644 --- a/care/templates/email/user_reset_password.html +++ b/care/templates/email/user_reset_password.html @@ -1,5 +1,5 @@ Hi, -Greetings from Coronasafe Network, +Greetings from Open Healthcare Network, Please click the following link to reset your password for your account with username {{username}} Click Here diff --git a/care/templates/email/user_reset_password.txt b/care/templates/email/user_reset_password.txt index df328882a5..2a2e8a703f 100644 --- a/care/templates/email/user_reset_password.txt +++ b/care/templates/email/user_reset_password.txt @@ -1,5 +1,5 @@ Hi, -Greetings from Coronasafe Network, +Greetings from Open Healthcare Network, Please click the following link to reset your password {{reset_password_url}} From 3de195418ae6b4cada34c09c315b523628869841 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:58:14 +0530 Subject: [PATCH 02/16] Store asset uptime --- care/facility/models/asset.py | 22 ++++++++++++++++ care/facility/tasks/asset_monitor.py | 38 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 care/facility/tasks/asset_monitor.py diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index ac14fba3a0..ccae9b4108 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -105,6 +105,28 @@ def __str__(self): return self.name +class AssetAvailabilityRecord(BaseModel): + class AvailabilityStatus(enum.Enum): + NOT_MONITORED = 0 + OPERATIONAL = 1 + DOWN = 2 + UNDER_MAINTENANCE = 3 + + AvailabilityStatusChoices = [(e.value, e.name) for e in AvailabilityStatus] + + asset = models.ForeignKey(Asset, on_delete=models.PROTECT, null=False, blank=False) + status = models.IntegerField( + choices=AvailabilityStatusChoices, default=AvailabilityStatus.UNKNOWN + ) + timestamp = models.DateTimeField(null=False, blank=False) + + class Meta: + ordering = ["-timestamp"] + + def __str__(self): + return f"{self.asset.name} - {self.status} - {self.time}" + + class UserDefaultAssetLocation(BaseModel): user = models.ForeignKey(User, on_delete=models.PROTECT, null=False, blank=False) location = models.ForeignKey( diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py new file mode 100644 index 0000000000..959be7b564 --- /dev/null +++ b/care/facility/tasks/asset_monitor.py @@ -0,0 +1,38 @@ +from datetime import datetime + +from celery import shared_task + +from care.facility.models.asset import Asset, AssetAvailabilityRecord +from care.utils.assetintegration.asset_classes import AssetClasses +from care.utils.assetintegration.base import BaseAssetIntegration + + +@shared_task +def check_asset_status(): + assets = Asset.objects.filter(is_working=True) + + for asset in assets: + asset_class: BaseAssetIntegration = AssetClasses[asset.asset_class].value( + { + **asset.meta, + "middleware_hostname": asset.current_location.facility.middleware_address, + } + ) + result = asset_class.api_get(asset_class.get_url("status")) + + if result and result.get("status"): + status = result.get("status", "-1") + if status == "-1": + continue + + new_status = AssetAvailabilityRecord.AvailabilityStatus(int(status)) + last_record = ( + AssetAvailabilityRecord.objects.filter(asset=asset) + .order_by("-timestamp") + .first() + ) + + if not last_record or last_record.status != new_status.value: + AssetAvailabilityRecord.objects.create( + asset=asset, status=new_status.value, timestamp=datetime.now() + ) From bbe249b8c84c2300d0fa274b79c36fe920f81e11 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 14 Jul 2023 18:37:32 +0530 Subject: [PATCH 03/16] Adapt to new API format --- .../0371_assetavailabilityrecord.py | 64 ++++++++++++++++ care/facility/models/asset.py | 10 +-- care/facility/tasks/__init__.py | 14 +++- care/facility/tasks/asset_monitor.py | 74 ++++++++++++++----- care/utils/assetintegration/asset_statuses.py | 8 ++ 5 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 care/facility/migrations/0371_assetavailabilityrecord.py create mode 100644 care/utils/assetintegration/asset_statuses.py diff --git a/care/facility/migrations/0371_assetavailabilityrecord.py b/care/facility/migrations/0371_assetavailabilityrecord.py new file mode 100644 index 0000000000..09c68419ea --- /dev/null +++ b/care/facility/migrations/0371_assetavailabilityrecord.py @@ -0,0 +1,64 @@ +# Generated by Django 4.2.2 on 2023-07-14 12:32 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0370_merge_20230705_1500"), + ] + + operations = [ + migrations.CreateModel( + name="AssetAvailabilityRecord", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ( + "status", + models.IntegerField( + choices=[ + (0, "NOT_MONITORED"), + (1, "OPERATIONAL"), + (2, "DOWN"), + (3, "UNDER_MAINTENANCE"), + ], + default=0, + ), + ), + ("timestamp", models.DateTimeField()), + ( + "asset", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="facility.asset" + ), + ), + ], + options={ + "ordering": ["-timestamp"], + }, + ), + ] diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index ccae9b4108..2173c2054b 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -9,6 +9,7 @@ from care.facility.models.mixins.permissions.asset import AssetsPermissionMixin from care.users.models import User, phone_number_regex_11 from care.utils.assetintegration.asset_classes import AssetClasses +from care.utils.assetintegration.asset_statuses import AvailabilityStatus from care.utils.models.base import BaseModel from care.utils.models.validators import JSONFieldSchemaValidator @@ -106,17 +107,12 @@ def __str__(self): class AssetAvailabilityRecord(BaseModel): - class AvailabilityStatus(enum.Enum): - NOT_MONITORED = 0 - OPERATIONAL = 1 - DOWN = 2 - UNDER_MAINTENANCE = 3 - AvailabilityStatusChoices = [(e.value, e.name) for e in AvailabilityStatus] asset = models.ForeignKey(Asset, on_delete=models.PROTECT, null=False, blank=False) status = models.IntegerField( - choices=AvailabilityStatusChoices, default=AvailabilityStatus.UNKNOWN + choices=AvailabilityStatusChoices, + default=AvailabilityStatus.NOT_MONITORED.value, ) timestamp = models.DateTimeField(null=False, blank=False) diff --git a/care/facility/tasks/__init__.py b/care/facility/tasks/__init__.py index 7ebf63cdaa..1a9383d32d 100644 --- a/care/facility/tasks/__init__.py +++ b/care/facility/tasks/__init__.py @@ -1,6 +1,7 @@ from celery import current_app from celery.schedules import crontab +from care.facility.tasks.asset_monitor import check_asset_status from care.facility.tasks.cleanup import delete_old_notifications from care.facility.tasks.summarisation import ( summarise_district_patient, @@ -19,12 +20,12 @@ def setup_periodic_tasks(sender, **kwargs): name="delete_old_notifications", ) sender.add_periodic_task( - crontab(hour="*/4", minute=59), + crontab(hour="*/4", minute="59"), summarise_triage.s(), name="summarise_triage", ) sender.add_periodic_task( - crontab(hour=23, minute=59), + crontab(hour="23", minute="59"), summarise_tests.s(), name="summarise_tests", ) @@ -34,12 +35,17 @@ def setup_periodic_tasks(sender, **kwargs): name="summarise_facility_capacity", ) sender.add_periodic_task( - crontab(hour="*/1", minute=59), + crontab(hour="*/1", minute="59"), summarise_patient.s(), name="summarise_patient", ) sender.add_periodic_task( - crontab(hour="*/1", minute=59), + crontab(hour="*/1", minute="59"), summarise_district_patient.s(), name="summarise_district_patient", ) + sender.add_periodic_task( + crontab(minute="*/30"), + check_asset_status.s(), + name="check_asset_status", + ) diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index 959be7b564..ce59ce120f 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -1,38 +1,74 @@ from datetime import datetime +from typing import Any from celery import shared_task from care.facility.models.asset import Asset, AssetAvailabilityRecord from care.utils.assetintegration.asset_classes import AssetClasses +from care.utils.assetintegration.asset_statuses import AvailabilityStatus from care.utils.assetintegration.base import BaseAssetIntegration @shared_task def check_asset_status(): - assets = Asset.objects.filter(is_working=True) + print("Checking Asset Status", datetime.now()) + assets = Asset.objects.all() + middleware_status_cache = {} for asset in assets: + if not asset.asset_class or not asset.meta.get("local_ip_address", None): + continue + hostname = asset.meta.get( + "middleware_hostname", + asset.current_location.facility.middleware_address, + ) asset_class: BaseAssetIntegration = AssetClasses[asset.asset_class].value( { **asset.meta, - "middleware_hostname": asset.current_location.facility.middleware_address, + "middleware_hostname": hostname, } ) - result = asset_class.api_get(asset_class.get_url("status")) - - if result and result.get("status"): - status = result.get("status", "-1") - if status == "-1": - continue - - new_status = AssetAvailabilityRecord.AvailabilityStatus(int(status)) - last_record = ( - AssetAvailabilityRecord.objects.filter(asset=asset) - .order_by("-timestamp") - .first() - ) - - if not last_record or last_record.status != new_status.value: - AssetAvailabilityRecord.objects.create( - asset=asset, status=new_status.value, timestamp=datetime.now() + try: + result: Any = {} + + if hostname in middleware_status_cache: + result = middleware_status_cache[hostname] + else: + result = asset_class.api_get(asset_class.get_url("/devices/status")) + middleware_status_cache[hostname] = result + + new_status = None + for status_record in result: + if asset.meta.get("local_ip_address") in status_record.get( + "status", {} + ): + new_status = status_record["status"][ + asset.meta.get("local_ip_address") + ] + else: + new_status = "not_monitored" + + last_record = ( + AssetAvailabilityRecord.objects.filter(asset=asset) + .order_by("-timestamp") + .first() ) + + if new_status == "up": + new_status = AvailabilityStatus.OPERATIONAL + elif new_status == "down": + new_status = AvailabilityStatus.DOWN + elif new_status == "maintenance": + new_status = AvailabilityStatus.UNDER_MAINTENANCE + else: + new_status = AvailabilityStatus.NOT_MONITORED + + if not last_record or last_record.status != new_status.value: + AssetAvailabilityRecord.objects.create( + asset=asset, + status=new_status.value, + timestamp=status_record.get("time", datetime.now()), + ) + + except Exception as e: + print("Error in Asset Status Check", e) diff --git a/care/utils/assetintegration/asset_statuses.py b/care/utils/assetintegration/asset_statuses.py new file mode 100644 index 0000000000..8a068df865 --- /dev/null +++ b/care/utils/assetintegration/asset_statuses.py @@ -0,0 +1,8 @@ +import enum + + +class AvailabilityStatus(enum.Enum): + NOT_MONITORED = 0 + OPERATIONAL = 1 + DOWN = 2 + UNDER_MAINTENANCE = 3 From f78b5fc632cfe3a90d327bac11b1019b50181b97 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:13:03 +0530 Subject: [PATCH 04/16] Add endpoint to fetch availability records --- care/facility/api/serializers/asset.py | 11 +++++++++++ care/facility/api/viewsets/asset.py | 13 +++++++++++++ config/api_router.py | 2 ++ 3 files changed, 26 insertions(+) diff --git a/care/facility/api/serializers/asset.py b/care/facility/api/serializers/asset.py index fbb9f44970..fe47aa6076 100644 --- a/care/facility/api/serializers/asset.py +++ b/care/facility/api/serializers/asset.py @@ -18,6 +18,7 @@ from care.facility.api.serializers.facility import FacilityBareMinimumSerializer from care.facility.models.asset import ( Asset, + AssetAvailabilityRecord, AssetLocation, AssetTransaction, UserDefaultAssetLocation, @@ -165,6 +166,16 @@ class Meta: exclude = ("deleted", "external_id") +class AssetAvailabilitySerializer(ModelSerializer): + id = UUIDField(source="external_id", read_only=True) + asset = AssetBareMinimumSerializer(read_only=True) + + class Meta: + model = AssetAvailabilityRecord + fields = ("asset", "status", "timestamp") + exclude = ("deleted", "external_id") + + class UserDefaultAssetLocationSerializer(ModelSerializer): location_object = AssetLocationSerializer(source="location", read_only=True) diff --git a/care/facility/api/viewsets/asset.py b/care/facility/api/viewsets/asset.py index 57ec16da51..2b5fc6e6b1 100644 --- a/care/facility/api/viewsets/asset.py +++ b/care/facility/api/viewsets/asset.py @@ -23,6 +23,7 @@ from rest_framework.viewsets import GenericViewSet from care.facility.api.serializers.asset import ( + AssetAvailabilitySerializer, AssetLocationSerializer, AssetSerializer, AssetTransactionSerializer, @@ -32,6 +33,7 @@ ) from care.facility.models.asset import ( Asset, + AssetAvailabilityRecord, AssetLocation, AssetTransaction, UserDefaultAssetLocation, @@ -128,6 +130,17 @@ def retrieve(self, request, *args, **kwargs): return Response(hit) +class AssetAvailabilityFilter(filters.FilterSet): + external_id = filters.CharFilter(field_name="asset__external_id") + + +class AssetAvailabilityViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): + queryset = AssetAvailabilityRecord.objects.all().select_related("asset") + serializer_class = AssetAvailabilitySerializer + filter_backends = (filters.DjangoFilterBackend,) + filterset_class = AssetAvailabilityFilter + + class AssetViewSet( ListModelMixin, RetrieveModelMixin, diff --git a/config/api_router.py b/config/api_router.py index 918ae39302..13d85f0da2 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -8,6 +8,7 @@ AmbulanceViewSet, ) from care.facility.api.viewsets.asset import ( + AssetAvailabilityViewSet, AssetLocationViewSet, AssetPublicViewSet, AssetTransactionViewSet, @@ -185,6 +186,7 @@ router.register("asset", AssetViewSet) router.register("asset_transaction", AssetTransactionViewSet) +router.register("asset_availability", AssetAvailabilityViewSet) patient_nested_router = NestedSimpleRouter(router, r"patient", lookup="patient") patient_nested_router.register(r"test_sample", PatientSampleViewSet) From 5f5efaf8b55acd1fc390a54181e61df95607e3f5 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:13:18 +0530 Subject: [PATCH 05/16] Merge migration conflict --- care/facility/migrations/0372_merge_20230714_1912.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 care/facility/migrations/0372_merge_20230714_1912.py diff --git a/care/facility/migrations/0372_merge_20230714_1912.py b/care/facility/migrations/0372_merge_20230714_1912.py new file mode 100644 index 0000000000..a8a2c5d409 --- /dev/null +++ b/care/facility/migrations/0372_merge_20230714_1912.py @@ -0,0 +1,12 @@ +# Generated by Django 4.2.2 on 2023-07-14 13:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0371_assetavailabilityrecord"), + ("facility", "0371_metaicd11diagnosis_chapter_and_more"), + ] + + operations = [] From 3d3aec1cca4acb0a3bfffff5472c2f2a6bec216f Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:53:32 +0530 Subject: [PATCH 06/16] Fix crash when running celery task for asset uptime --- care/facility/api/serializers/asset.py | 1 - ...alter_assetavailabilityrecord_timestamp.py | 17 +++++++++ ...etavailabilityrecord_timestamp_and_more.py | 21 ++++++++++ care/facility/models/asset.py | 14 ++++++- care/facility/tasks/asset_monitor.py | 38 ++++++++++++------- care/utils/assetintegration/base.py | 4 +- tests/test_facility_tasks.py | 14 +++++++ 7 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py create mode 100644 care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py create mode 100644 tests/test_facility_tasks.py diff --git a/care/facility/api/serializers/asset.py b/care/facility/api/serializers/asset.py index fe47aa6076..7e672c4a01 100644 --- a/care/facility/api/serializers/asset.py +++ b/care/facility/api/serializers/asset.py @@ -172,7 +172,6 @@ class AssetAvailabilitySerializer(ModelSerializer): class Meta: model = AssetAvailabilityRecord - fields = ("asset", "status", "timestamp") exclude = ("deleted", "external_id") diff --git a/care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py b/care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py new file mode 100644 index 0000000000..a6737ebdaa --- /dev/null +++ b/care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.2 on 2023-07-17 06:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0372_merge_20230714_1912"), + ] + + operations = [ + migrations.AlterField( + model_name="assetavailabilityrecord", + name="timestamp", + field=models.DateTimeField(unique=True), + ), + ] diff --git a/care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py b/care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py new file mode 100644 index 0000000000..a920527d1d --- /dev/null +++ b/care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.2 on 2023-07-17 06:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0373_alter_assetavailabilityrecord_timestamp"), + ] + + operations = [ + migrations.AlterField( + model_name="assetavailabilityrecord", + name="timestamp", + field=models.DateTimeField(), + ), + migrations.AlterUniqueTogether( + name="assetavailabilityrecord", + unique_together={("asset", "timestamp")}, + ), + ] diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index 2173c2054b..3b3fe4cc27 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -107,6 +107,17 @@ def __str__(self): class AssetAvailabilityRecord(BaseModel): + """ + Model to store the availability status of an asset at a particular timestamp. + + Fields: + - asset: ForeignKey to Asset model + - status: IntegerField with choices from AvailabilityStatus enum + - timestamp: DateTimeField to store the timestamp of the availability record + + Note: A pair of asset and timestamp together should be unique, not just the timestamp alone. + """ + AvailabilityStatusChoices = [(e.value, e.name) for e in AvailabilityStatus] asset = models.ForeignKey(Asset, on_delete=models.PROTECT, null=False, blank=False) @@ -117,10 +128,11 @@ class AssetAvailabilityRecord(BaseModel): timestamp = models.DateTimeField(null=False, blank=False) class Meta: + unique_together = (("asset", "timestamp"),) ordering = ["-timestamp"] def __str__(self): - return f"{self.asset.name} - {self.status} - {self.time}" + return f"{self.asset.name} - {self.status} - {self.timestamp}" class UserDefaultAssetLocation(BaseModel): diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index ce59ce120f..3c83cf6a06 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -18,24 +18,32 @@ def check_asset_status(): for asset in assets: if not asset.asset_class or not asset.meta.get("local_ip_address", None): continue - hostname = asset.meta.get( - "middleware_hostname", - asset.current_location.facility.middleware_address, - ) - asset_class: BaseAssetIntegration = AssetClasses[asset.asset_class].value( - { - **asset.meta, - "middleware_hostname": hostname, - } - ) try: + hostname = asset.meta.get( + "middleware_hostname", + asset.current_location.facility.middleware_address, + ) + asset_class: BaseAssetIntegration = AssetClasses[asset.asset_class].value( + { + **asset.meta, + "middleware_hostname": hostname, + } + ) result: Any = {} if hostname in middleware_status_cache: result = middleware_status_cache[hostname] else: - result = asset_class.api_get(asset_class.get_url("/devices/status")) - middleware_status_cache[hostname] = result + try: + result = asset_class.api_get(asset_class.get_url("devices/status")) + middleware_status_cache[hostname] = result + except Exception as e: + print("Error in Asset Status Check", e) + middleware_status_cache[hostname] = None + continue + + if not result: + continue new_status = None for status_record in result: @@ -63,7 +71,11 @@ def check_asset_status(): else: new_status = AvailabilityStatus.NOT_MONITORED - if not last_record or last_record.status != new_status.value: + if not last_record or ( + datetime.fromisoformat(status_record.get("time")) + > last_record.timestamp + and last_record.status != new_status.value + ): AssetAvailabilityRecord.objects.create( asset=asset, status=new_status.value, diff --git a/care/utils/assetintegration/base.py b/care/utils/assetintegration/base.py index f703c277ac..92d318c3a5 100644 --- a/care/utils/assetintegration/base.py +++ b/care/utils/assetintegration/base.py @@ -46,9 +46,9 @@ def api_get(self, url, data=None): headers={"Authorization": (self.auth_header_type + generate_jwt())}, ) try: - response = req.json() if req.status_code >= 400: - raise APIException(response, req.status_code) + raise APIException(req.text, req.status_code) + response = req.json() return response except json.decoder.JSONDecodeError: return {"error": "Invalid Response"} diff --git a/tests/test_facility_tasks.py b/tests/test_facility_tasks.py new file mode 100644 index 0000000000..19b6639e74 --- /dev/null +++ b/tests/test_facility_tasks.py @@ -0,0 +1,14 @@ +from unittest.mock import patch + +from celery import Celery + +from care.facility.tasks import setup_periodic_tasks + + +@patch("care.facility.tasks.asset_monitor.check_asset_status.delay") +def test_setup_periodic_tasks(mock_check_asset_status): + app = Celery() + setup_periodic_tasks(app) + + # Ensure that check_asset_status is called immediately + mock_check_asset_status.assert_called_once() From 509fffaee2ff5001be3586f1dd8dd8f5159943ce Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:42:18 +0530 Subject: [PATCH 07/16] Update care/facility/models/asset.py Co-authored-by: Aakash Singh --- care/facility/models/asset.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index 3b3fe4cc27..1bdc3740b1 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -118,11 +118,9 @@ class AssetAvailabilityRecord(BaseModel): Note: A pair of asset and timestamp together should be unique, not just the timestamp alone. """ - AvailabilityStatusChoices = [(e.value, e.name) for e in AvailabilityStatus] - asset = models.ForeignKey(Asset, on_delete=models.PROTECT, null=False, blank=False) status = models.IntegerField( - choices=AvailabilityStatusChoices, + choices=AvailabilityStatus.choices, default=AvailabilityStatus.NOT_MONITORED.value, ) timestamp = models.DateTimeField(null=False, blank=False) From 3513644d47cc7bb6d959e5b94ed2268716f77f55 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:42:34 +0530 Subject: [PATCH 08/16] Update care/facility/tasks/asset_monitor.py Co-authored-by: Aakash Singh --- care/facility/tasks/asset_monitor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index 3c83cf6a06..0185963d89 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -23,18 +23,18 @@ def check_asset_status(): "middleware_hostname", asset.current_location.facility.middleware_address, ) - asset_class: BaseAssetIntegration = AssetClasses[asset.asset_class].value( - { - **asset.meta, - "middleware_hostname": hostname, - } - ) result: Any = {} if hostname in middleware_status_cache: result = middleware_status_cache[hostname] else: try: + asset_class: BaseAssetIntegration = AssetClasses[asset.asset_class].value( + { + **asset.meta, + "middleware_hostname": hostname, + } + ) result = asset_class.api_get(asset_class.get_url("devices/status")) middleware_status_cache[hostname] = result except Exception as e: From 6ac142dc9e8dcb6336afd5ac1d731738fcc20ccc Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Mon, 17 Jul 2023 21:55:55 +0530 Subject: [PATCH 09/16] use IntegerChoices instead of Enum for assetstatus --- ...75_alter_assetavailabilityrecord_status.py | 25 +++++++++++++++++++ care/facility/models/asset.py | 8 +++++- care/facility/tasks/asset_monitor.py | 11 +++++--- care/utils/assetintegration/asset_statuses.py | 8 ------ 4 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 care/facility/migrations/0375_alter_assetavailabilityrecord_status.py delete mode 100644 care/utils/assetintegration/asset_statuses.py diff --git a/care/facility/migrations/0375_alter_assetavailabilityrecord_status.py b/care/facility/migrations/0375_alter_assetavailabilityrecord_status.py new file mode 100644 index 0000000000..c361c64b95 --- /dev/null +++ b/care/facility/migrations/0375_alter_assetavailabilityrecord_status.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.2 on 2023-07-17 16:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0374_alter_assetavailabilityrecord_timestamp_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="assetavailabilityrecord", + name="status", + field=models.IntegerField( + choices=[ + (0, "Not Monitored"), + (1, "Operational"), + (2, "Down"), + (3, "Under Maintenance"), + ], + default=0, + ), + ), + ] diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index 1bdc3740b1..063538f821 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -9,7 +9,6 @@ from care.facility.models.mixins.permissions.asset import AssetsPermissionMixin from care.users.models import User, phone_number_regex_11 from care.utils.assetintegration.asset_classes import AssetClasses -from care.utils.assetintegration.asset_statuses import AvailabilityStatus from care.utils.models.base import BaseModel from care.utils.models.validators import JSONFieldSchemaValidator @@ -18,6 +17,13 @@ def get_random_asset_id(): return str(uuid.uuid4()) +class AvailabilityStatus(models.IntegerChoices): + NOT_MONITORED = 0 + OPERATIONAL = 1 + DOWN = 2 + UNDER_MAINTENANCE = 3 + + class AssetLocation(BaseModel, AssetsPermissionMixin): """ This model is also used to store rooms that the assets are in, Since these rooms are mapped to diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index 0185963d89..4b31c7f86b 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -3,9 +3,12 @@ from celery import shared_task -from care.facility.models.asset import Asset, AssetAvailabilityRecord +from care.facility.models.asset import ( + Asset, + AssetAvailabilityRecord, + AvailabilityStatus, +) from care.utils.assetintegration.asset_classes import AssetClasses -from care.utils.assetintegration.asset_statuses import AvailabilityStatus from care.utils.assetintegration.base import BaseAssetIntegration @@ -29,7 +32,9 @@ def check_asset_status(): result = middleware_status_cache[hostname] else: try: - asset_class: BaseAssetIntegration = AssetClasses[asset.asset_class].value( + asset_class: BaseAssetIntegration = AssetClasses[ + asset.asset_class + ].value( { **asset.meta, "middleware_hostname": hostname, diff --git a/care/utils/assetintegration/asset_statuses.py b/care/utils/assetintegration/asset_statuses.py deleted file mode 100644 index 8a068df865..0000000000 --- a/care/utils/assetintegration/asset_statuses.py +++ /dev/null @@ -1,8 +0,0 @@ -import enum - - -class AvailabilityStatus(enum.Enum): - NOT_MONITORED = 0 - OPERATIONAL = 1 - DOWN = 2 - UNDER_MAINTENANCE = 3 From ebd5fde0dbdc53e56f6835d72626a84d52118cef Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 18 Jul 2023 08:23:52 +0530 Subject: [PATCH 10/16] Merge migrations --- ...ord.py => 0372_assetavailabilityrecord.py} | 5 +++-- .../migrations/0372_merge_20230714_1912.py | 12 ----------- ...alter_assetavailabilityrecord_timestamp.py | 17 --------------- ...etavailabilityrecord_timestamp_and_more.py | 21 ------------------- 4 files changed, 3 insertions(+), 52 deletions(-) rename care/facility/migrations/{0371_assetavailabilityrecord.py => 0372_assetavailabilityrecord.py} (91%) delete mode 100644 care/facility/migrations/0372_merge_20230714_1912.py delete mode 100644 care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py delete mode 100644 care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py diff --git a/care/facility/migrations/0371_assetavailabilityrecord.py b/care/facility/migrations/0372_assetavailabilityrecord.py similarity index 91% rename from care/facility/migrations/0371_assetavailabilityrecord.py rename to care/facility/migrations/0372_assetavailabilityrecord.py index 09c68419ea..5c5fc167ab 100644 --- a/care/facility/migrations/0371_assetavailabilityrecord.py +++ b/care/facility/migrations/0372_assetavailabilityrecord.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.2 on 2023-07-14 12:32 +# Generated by Django 4.2.2 on 2023-07-18 02:53 import uuid @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ("facility", "0370_merge_20230705_1500"), + ("facility", "0371_metaicd11diagnosis_chapter_and_more"), ] operations = [ @@ -59,6 +59,7 @@ class Migration(migrations.Migration): ], options={ "ordering": ["-timestamp"], + "unique_together": {("asset", "timestamp")}, }, ), ] diff --git a/care/facility/migrations/0372_merge_20230714_1912.py b/care/facility/migrations/0372_merge_20230714_1912.py deleted file mode 100644 index a8a2c5d409..0000000000 --- a/care/facility/migrations/0372_merge_20230714_1912.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-14 13:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("facility", "0371_assetavailabilityrecord"), - ("facility", "0371_metaicd11diagnosis_chapter_and_more"), - ] - - operations = [] diff --git a/care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py b/care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py deleted file mode 100644 index a6737ebdaa..0000000000 --- a/care/facility/migrations/0373_alter_assetavailabilityrecord_timestamp.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-17 06:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("facility", "0372_merge_20230714_1912"), - ] - - operations = [ - migrations.AlterField( - model_name="assetavailabilityrecord", - name="timestamp", - field=models.DateTimeField(unique=True), - ), - ] diff --git a/care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py b/care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py deleted file mode 100644 index a920527d1d..0000000000 --- a/care/facility/migrations/0374_alter_assetavailabilityrecord_timestamp_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-17 06:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("facility", "0373_alter_assetavailabilityrecord_timestamp"), - ] - - operations = [ - migrations.AlterField( - model_name="assetavailabilityrecord", - name="timestamp", - field=models.DateTimeField(), - ), - migrations.AlterUniqueTogether( - name="assetavailabilityrecord", - unique_together={("asset", "timestamp")}, - ), - ] From b2f8ebae837f60f5ff8bc4b68b493ab8b4335394 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 18 Jul 2023 08:41:00 +0530 Subject: [PATCH 11/16] Switch to string values for AvailabilityRecord --- care/facility/models/asset.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index 063538f821..1447ccb470 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -17,11 +17,11 @@ def get_random_asset_id(): return str(uuid.uuid4()) -class AvailabilityStatus(models.IntegerChoices): - NOT_MONITORED = 0 - OPERATIONAL = 1 - DOWN = 2 - UNDER_MAINTENANCE = 3 +class AvailabilityStatus(models.TextChoices): + NOT_MONITORED = "Not Monitored" + OPERATIONAL = "Operational" + DOWN = "Down" + UNDER_MAINTENANCE = "Under Maintenance" class AssetLocation(BaseModel, AssetsPermissionMixin): @@ -118,16 +118,16 @@ class AssetAvailabilityRecord(BaseModel): Fields: - asset: ForeignKey to Asset model - - status: IntegerField with choices from AvailabilityStatus enum + - status: TextField with choices from AvailabilityStatus - timestamp: DateTimeField to store the timestamp of the availability record Note: A pair of asset and timestamp together should be unique, not just the timestamp alone. """ asset = models.ForeignKey(Asset, on_delete=models.PROTECT, null=False, blank=False) - status = models.IntegerField( + status = models.TextField( choices=AvailabilityStatus.choices, - default=AvailabilityStatus.NOT_MONITORED.value, + default=AvailabilityStatus.NOT_MONITORED, ) timestamp = models.DateTimeField(null=False, blank=False) From cbf4e303e390d72e1f312ffc3df255cb77dff352 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 18 Jul 2023 08:41:17 +0530 Subject: [PATCH 12/16] use logging instead of print --- care/facility/tasks/asset_monitor.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index 4b31c7f86b..841937ec0f 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime from typing import Any @@ -11,10 +12,14 @@ from care.utils.assetintegration.asset_classes import AssetClasses from care.utils.assetintegration.base import BaseAssetIntegration +logger = logging.getLogger(__name__) + @shared_task def check_asset_status(): print("Checking Asset Status", datetime.now()) + logger.info(f"Checking Asset Status: {datetime.now()}") + assets = Asset.objects.all() middleware_status_cache = {} @@ -42,8 +47,8 @@ def check_asset_status(): ) result = asset_class.api_get(asset_class.get_url("devices/status")) middleware_status_cache[hostname] = result - except Exception as e: - print("Error in Asset Status Check", e) + except Exception: + logger.exception("Error in Asset Status Check - Fetching Status") middleware_status_cache[hostname] = None continue @@ -87,5 +92,5 @@ def check_asset_status(): timestamp=status_record.get("time", datetime.now()), ) - except Exception as e: - print("Error in Asset Status Check", e) + except Exception: + logger.exception("Error in Asset Status Check") From 9aef0f1a4df7525fb950d93a215daae4d219a419 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:32:05 +0530 Subject: [PATCH 13/16] Switch to string values --- .../0372_assetavailabilityrecord.py | 15 +++++------ ...75_alter_assetavailabilityrecord_status.py | 25 ------------------- care/facility/models/asset.py | 5 ++-- 3 files changed, 11 insertions(+), 34 deletions(-) delete mode 100644 care/facility/migrations/0375_alter_assetavailabilityrecord_status.py diff --git a/care/facility/migrations/0372_assetavailabilityrecord.py b/care/facility/migrations/0372_assetavailabilityrecord.py index 5c5fc167ab..2d3dc2daf4 100644 --- a/care/facility/migrations/0372_assetavailabilityrecord.py +++ b/care/facility/migrations/0372_assetavailabilityrecord.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.2 on 2023-07-18 02:53 +# Generated by Django 4.2.2 on 2023-07-18 05:00 import uuid @@ -39,14 +39,15 @@ class Migration(migrations.Migration): ("deleted", models.BooleanField(db_index=True, default=False)), ( "status", - models.IntegerField( + models.CharField( choices=[ - (0, "NOT_MONITORED"), - (1, "OPERATIONAL"), - (2, "DOWN"), - (3, "UNDER_MAINTENANCE"), + ("Not Monitored", "Not Monitored"), + ("Operational", "Operational"), + ("Down", "Down"), + ("Under Maintenance", "Under Maintenance"), ], - default=0, + default="Not Monitored", + max_length=20, ), ), ("timestamp", models.DateTimeField()), diff --git a/care/facility/migrations/0375_alter_assetavailabilityrecord_status.py b/care/facility/migrations/0375_alter_assetavailabilityrecord_status.py deleted file mode 100644 index c361c64b95..0000000000 --- a/care/facility/migrations/0375_alter_assetavailabilityrecord_status.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.2.2 on 2023-07-17 16:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("facility", "0374_alter_assetavailabilityrecord_timestamp_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="assetavailabilityrecord", - name="status", - field=models.IntegerField( - choices=[ - (0, "Not Monitored"), - (1, "Operational"), - (2, "Down"), - (3, "Under Maintenance"), - ], - default=0, - ), - ), - ] diff --git a/care/facility/models/asset.py b/care/facility/models/asset.py index 1447ccb470..fdeaff4263 100644 --- a/care/facility/models/asset.py +++ b/care/facility/models/asset.py @@ -118,16 +118,17 @@ class AssetAvailabilityRecord(BaseModel): Fields: - asset: ForeignKey to Asset model - - status: TextField with choices from AvailabilityStatus + - status: CharField with choices from AvailabilityStatus - timestamp: DateTimeField to store the timestamp of the availability record Note: A pair of asset and timestamp together should be unique, not just the timestamp alone. """ asset = models.ForeignKey(Asset, on_delete=models.PROTECT, null=False, blank=False) - status = models.TextField( + status = models.CharField( choices=AvailabilityStatus.choices, default=AvailabilityStatus.NOT_MONITORED, + max_length=20, ) timestamp = models.DateTimeField(null=False, blank=False) From 0ed002de4437e5ffe63ebb9309d10a8739b0f137 Mon Sep 17 00:00:00 2001 From: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:06:46 +0530 Subject: [PATCH 14/16] Add tests --- care/facility/tasks/asset_monitor.py | 7 ++- .../tests/test_asset_availability_api.py | 56 +++++++++++++++++++ care/facility/tests/test_medibase_api.py | 16 +++--- .../tests/test_patient_daily_rounds_api.py | 2 +- care/users/tests/test_facility_user_create.py | 4 +- tests/test_facility_tasks.py | 14 ----- 6 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 care/facility/tests/test_asset_availability_api.py delete mode 100644 tests/test_facility_tasks.py diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index 841937ec0f..6274bd3c85 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -3,6 +3,7 @@ from typing import Any from celery import shared_task +from django.utils import timezone from care.facility.models.asset import ( Asset, @@ -17,8 +18,8 @@ @shared_task def check_asset_status(): - print("Checking Asset Status", datetime.now()) - logger.info(f"Checking Asset Status: {datetime.now()}") + print("Checking Asset Status", timezone.now()) + logger.info(f"Checking Asset Status: {timezone.now()}") assets = Asset.objects.all() middleware_status_cache = {} @@ -89,7 +90,7 @@ def check_asset_status(): AssetAvailabilityRecord.objects.create( asset=asset, status=new_status.value, - timestamp=status_record.get("time", datetime.now()), + timestamp=status_record.get("time", timezone.now()), ) except Exception: diff --git a/care/facility/tests/test_asset_availability_api.py b/care/facility/tests/test_asset_availability_api.py new file mode 100644 index 0000000000..068982d001 --- /dev/null +++ b/care/facility/tests/test_asset_availability_api.py @@ -0,0 +1,56 @@ +from django.utils import timezone +from rest_framework import status +from rest_framework.test import APIRequestFactory, APITestCase + +from care.facility.api.viewsets.asset import AssetAvailabilityViewSet +from care.facility.models import Asset, AssetAvailabilityRecord, AssetLocation +from care.facility.models.asset import AvailabilityStatus +from care.facility.tests.mixins import TestClassMixin +from care.utils.tests.test_base import TestBase + + +class AssetAvailabilityViewSetTestCase(TestBase, TestClassMixin, APITestCase): + def setUp(self): + self.factory = APIRequestFactory() + state = self.create_state() + district = self.create_district(state=state) + self.user = self.create_user(district=district, username="test user") + facility = self.create_facility(district=district, user=self.user) + self.asset_from_location = AssetLocation.objects.create( + name="asset from location", location_type=1, facility=facility + ) + self.asset_to_location = AssetLocation.objects.create( + name="asset to location", location_type=1, facility=facility + ) + self.asset = Asset.objects.create( + name="Test Asset", current_location=self.asset_from_location, asset_type=50 + ) + + self.asset_availability = AssetAvailabilityRecord.objects.create( + asset=self.asset, + status=AvailabilityStatus.OPERATIONAL.value, + timestamp=timezone.now(), + ) + + def test_list_asset_availability(self): + response = self.new_request( + ("/api/v1/asset_availability/",), + {"get": "list"}, + AssetAvailabilityViewSet, + True, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data["results"][0]["status"], AvailabilityStatus.OPERATIONAL.value + ) + + def test_retrieve_asset_availability(self): + response = self.new_request( + (f"/api/v1/asset_availability/{self.asset_availability.id}/",), + {"get": "retrieve"}, + AssetAvailabilityViewSet, + True, + {"pk": self.asset_availability.id}, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["status"], AvailabilityStatus.OPERATIONAL.value) diff --git a/care/facility/tests/test_medibase_api.py b/care/facility/tests/test_medibase_api.py index 34ce13d7d5..ec6b53afad 100644 --- a/care/facility/tests/test_medibase_api.py +++ b/care/facility/tests/test_medibase_api.py @@ -9,17 +9,17 @@ def get_url(self, query=None): def test_search_by_name_exact_word(self): response = self.client.get(self.get_url(query="dolo")) - self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data[0]["name"], "DOLO") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["name"], "DOLO") def test_search_by_generic_exact_word(self): response = self.client.get(self.get_url(query="pAraCetAmoL")) - self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data[0]["generic"], "paracetamol") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["generic"], "paracetamol") def test_search_by_name_and_generic_exact_word(self): response = self.client.get(self.get_url(query="panadol paracetamol")) - self.assertEquals(response.status_code, status.HTTP_200_OK) - self.assertEquals(response.data[0]["name"], "PANADOL") - self.assertEquals(response.data[0]["generic"], "paracetamol") - self.assertEquals(response.data[0]["company"], "GSK") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data[0]["name"], "PANADOL") + self.assertEqual(response.data[0]["generic"], "paracetamol") + self.assertEqual(response.data[0]["company"], "GSK") diff --git a/care/facility/tests/test_patient_daily_rounds_api.py b/care/facility/tests/test_patient_daily_rounds_api.py index 4b675274c6..22733cfadb 100644 --- a/care/facility/tests/test_patient_daily_rounds_api.py +++ b/care/facility/tests/test_patient_daily_rounds_api.py @@ -10,4 +10,4 @@ def get_url(self, external_consultation_id=None): def test_external_consultation_does_not_exists_returns_404(self): sample_uuid = "e4a3d84a-d678-4992-9287-114f029046d8" response = self.client.get(self.get_url(sample_uuid)) - self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/care/users/tests/test_facility_user_create.py b/care/users/tests/test_facility_user_create.py index 75849c3180..66ae51d4f6 100644 --- a/care/users/tests/test_facility_user_create.py +++ b/care/users/tests/test_facility_user_create.py @@ -55,7 +55,7 @@ def test_create_facility_user__should_fail__when_higher_level(self): response = self.client.post(self.get_url(), data=data, format="json") # Test Creation - self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_create_facility_user__should_fail__when_different_location(self): new_district = self.clone_object(self.district) @@ -64,4 +64,4 @@ def test_create_facility_user__should_fail__when_different_location(self): response = self.client.post(self.get_url(), data=data, format="json") # Test Creation - self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/tests/test_facility_tasks.py b/tests/test_facility_tasks.py deleted file mode 100644 index 19b6639e74..0000000000 --- a/tests/test_facility_tasks.py +++ /dev/null @@ -1,14 +0,0 @@ -from unittest.mock import patch - -from celery import Celery - -from care.facility.tasks import setup_periodic_tasks - - -@patch("care.facility.tasks.asset_monitor.check_asset_status.delay") -def test_setup_periodic_tasks(mock_check_asset_status): - app = Celery() - setup_periodic_tasks(app) - - # Ensure that check_asset_status is called immediately - mock_check_asset_status.assert_called_once() From b37baf3b3484eda91a2024c0545e3c66f32c4ab9 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Tue, 18 Jul 2023 13:06:35 +0530 Subject: [PATCH 15/16] fix tests --- care/facility/tests/test_asset_api.py | 20 ++++++++------- .../tests/test_asset_availability_api.py | 25 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/care/facility/tests/test_asset_api.py b/care/facility/tests/test_asset_api.py index dbb8c27d42..36cb493e49 100644 --- a/care/facility/tests/test_asset_api.py +++ b/care/facility/tests/test_asset_api.py @@ -11,17 +11,19 @@ class AssetViewSetTestCase(TestBase, TestClassMixin, APITestCase): asset_id = None - def setUp(self): - self.factory = APIRequestFactory() - state = self.create_state() - district = self.create_district(state=state) - self.user = self.create_user(district=district, username="test user") - facility = self.create_facility(district=district, user=self.user) - self.asset1_location = AssetLocation.objects.create( + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.factory = APIRequestFactory() + state = cls.create_state() + district = cls.create_district(state=state) + cls.user = cls.create_user(district=district, username="test user") + facility = cls.create_facility(district=district, user=cls.user) + cls.asset1_location = AssetLocation.objects.create( name="asset1 location", location_type=1, facility=facility ) - self.asset = Asset.objects.create( - name="Test Asset", current_location=self.asset1_location, asset_type=50 + cls.asset = Asset.objects.create( + name="Test Asset", current_location=cls.asset1_location, asset_type=50 ) def test_list_assets(self): diff --git a/care/facility/tests/test_asset_availability_api.py b/care/facility/tests/test_asset_availability_api.py index 068982d001..65b7d36e40 100644 --- a/care/facility/tests/test_asset_availability_api.py +++ b/care/facility/tests/test_asset_availability_api.py @@ -10,24 +10,25 @@ class AssetAvailabilityViewSetTestCase(TestBase, TestClassMixin, APITestCase): - def setUp(self): - self.factory = APIRequestFactory() - state = self.create_state() - district = self.create_district(state=state) - self.user = self.create_user(district=district, username="test user") - facility = self.create_facility(district=district, user=self.user) - self.asset_from_location = AssetLocation.objects.create( + @classmethod + def setUp(cls): + cls.factory = APIRequestFactory() + state = cls.create_state() + district = cls.create_district(state=state) + cls.user = cls.create_user(district=district, username="test user") + facility = cls.create_facility(district=district, user=cls.user) + cls.asset_from_location = AssetLocation.objects.create( name="asset from location", location_type=1, facility=facility ) - self.asset_to_location = AssetLocation.objects.create( + cls.asset_to_location = AssetLocation.objects.create( name="asset to location", location_type=1, facility=facility ) - self.asset = Asset.objects.create( - name="Test Asset", current_location=self.asset_from_location, asset_type=50 + cls.asset = Asset.objects.create( + name="Test Asset", current_location=cls.asset_from_location, asset_type=50 ) - self.asset_availability = AssetAvailabilityRecord.objects.create( - asset=self.asset, + cls.asset_availability = AssetAvailabilityRecord.objects.create( + asset=cls.asset, status=AvailabilityStatus.OPERATIONAL.value, timestamp=timezone.now(), ) From e38999c986c6759b5347d8c0644f0a616350a7ee Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:14:44 +0530 Subject: [PATCH 16/16] Remove print statement --- care/facility/tasks/asset_monitor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index 6274bd3c85..82a0bcd0ee 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -18,7 +18,6 @@ @shared_task def check_asset_status(): - print("Checking Asset Status", timezone.now()) logger.info(f"Checking Asset Status: {timezone.now()}") assets = Asset.objects.all()