From 7c597bf9027f1b13a0bfc16ff61c77c2fadc7f95 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Mon, 9 Sep 2024 09:30:13 -0400 Subject: [PATCH] client: fix import in patient property code This allows client.patient to work again (i.e. to transparently request client.patient_id from the server). This was a regression introduced in 4.2.0. --- fhirclient/client.py | 12 ++++----- tests/client_test.py | 58 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/fhirclient/client.py b/fhirclient/client.py index f492a6dfc..d6d12de4d 100644 --- a/fhirclient/client.py +++ b/fhirclient/client.py @@ -2,7 +2,7 @@ from .server import FHIRServer, FHIRUnauthorizedException, FHIRNotFoundException -__version__ = '4.2.0' +__version__ = '4.2.1' __author__ = 'SMART Platforms Team' __license__ = 'APACHE2' __copyright__ = "Copyright 2017 Boston Children's Hospital" @@ -166,16 +166,16 @@ def _handle_launch_context(self, ctx): @property def patient(self): if self._patient is None and self.patient_id is not None and self.ready: - import models.patient + from fhirclient.models.patient import Patient try: logger.debug("SMART: Attempting to read Patient {0}".format(self.patient_id)) - self._patient = models.patient.Patient.read(self.patient_id, self.server) - except FHIRUnauthorizedException as e: + self._patient = Patient.read(self.patient_id, self.server) + except FHIRUnauthorizedException: if self.reauthorize(): logger.debug("SMART: Attempting to read Patient {0} after reauthorizing" .format(self.patient_id)) - self._patient = models.patient.Patient.read(self.patient_id, self.server) - except FHIRNotFoundException as e: + self._patient = Patient.read(self.patient_id, self.server) + except FHIRNotFoundException: logger.warning("SMART: Patient with id {0} not found".format(self.patient_id)) self.patient_id = None self.save_state() diff --git a/tests/client_test.py b/tests/client_test.py index be1315631..21d0fdb8d 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -1,6 +1,13 @@ import unittest +from unittest import mock from fhirclient.client import FHIRClient +from fhirclient.server import FHIRNotFoundException, FHIRUnauthorizedException + +# Smallest valid-but-fake client state +MIN_STATE = { + "server": {"base_uri": "http://example.com/fhir"}, +} class TestClient(unittest.TestCase): @@ -39,3 +46,54 @@ def test_load_from_state(self): client.from_state({'app_id': 'NewID', 'server': state['server']}) self.assertEqual('NewID', client.app_id) self.assertEqual('LaunchToken', client.launch_token) + + @mock.patch("fhirclient.models.patient.Patient.read") + def test_patient_property_happy_path(self, mock_read): + save_func = mock.MagicMock() + + # Verify that we gracefully handle no patient_id being given + client = FHIRClient(state=MIN_STATE, save_func=save_func) + self.assertIsNone(client.patient) + self.assertEqual(mock_read.call_count, 0) + self.assertEqual(save_func.call_count, 0) + + # Verify we expose the provided patient ID as a Patient object + client = FHIRClient(state={"patient_id": "P123", **MIN_STATE}, save_func=save_func) + self.assertIsNotNone(client.patient) + self.assertEqual(mock_read.call_count, 1) + self.assertEqual(mock_read.call_args, mock.call("P123", client.server)) + self.assertEqual(save_func.call_count, 1) + + @mock.patch("fhirclient.models.patient.Patient.read") + @mock.patch("fhirclient.client.FHIRClient.reauthorize") + def test_patient_property_unauthorized(self, mock_reauthorize, mock_read): + """We should attempt to reauthorize and re-request the patient""" + + client = FHIRClient(state={"patient_id": "P123", **MIN_STATE}) + + # First try with a failed re-authorize + mock_read.side_effect = FHIRUnauthorizedException("response") + mock_reauthorize.return_value = False + self.assertIsNone(client.patient) + self.assertEqual(mock_read.call_count, 1) + self.assertEqual(mock_reauthorize.call_count, 1) + + # Then with a successful re-authorize + mock_read.reset_mock() + mock_read.side_effect = [FHIRUnauthorizedException("response"), mock.MagicMock()] + mock_reauthorize.reset_mock() + mock_reauthorize.return_value = True + self.assertIsNotNone(client.patient) + self.assertEqual(mock_read.call_count, 2) + self.assertEqual(mock_reauthorize.call_count, 1) + + @mock.patch("fhirclient.models.patient.Patient.read") + def test_patient_property_not_found(self, mock_read): + """We should attempt to reauthorize and re-request the patient""" + mock_read.side_effect = FHIRNotFoundException("response") + + client = FHIRClient(state={"patient_id": "P123", **MIN_STATE}) + self.assertEqual(client.patient_id, "P123") # sanity check before we start + + self.assertIsNone(client.patient) + self.assertIsNone(client.patient_id) # we clear out the patient id