diff --git a/Makefile b/Makefile index 52f9cfd..30cf5ef 100644 --- a/Makefile +++ b/Makefile @@ -135,7 +135,7 @@ app-docker-run: docker run -p 9000:9000 "$(PROXYGEN_DOCKER_REGISTRY_URL):$(CONTAINER_TAG)" app-unit-test: - uv run pytest app --cov=app --cov-fail-under=80 + uv run pytest app --cov=app --cov-fail-under=80 $(PYTEST_ARGS) # ============================================================================== # Sandbox Commands diff --git a/app/api/domain/forward_response_model.py b/app/api/domain/forward_response_model.py index ac694f6..0a82706 100644 --- a/app/api/domain/forward_response_model.py +++ b/app/api/domain/forward_response_model.py @@ -2,6 +2,10 @@ from pydantic.alias_generators import to_camel +class Permissions(BaseModel): + """A data model that encapsulates all the essential permissions data.""" + + class Demographics(BaseModel): """A data model that encapsulates all the essential demographic data.""" @@ -10,6 +14,8 @@ class Demographics(BaseModel): first_name: str surname: str title: str + date_of_birth: str + permissions: Permissions class ForwardResponse(BaseModel): @@ -19,7 +25,8 @@ class ForwardResponse(BaseModel): session_id: str supplier: str - proxy: Demographics + ods_code: str + user: Demographics patients: list[Demographics] @field_validator("patients") diff --git a/app/api/domain/tests/test_forward_response_model.py b/app/api/domain/tests/test_forward_response_model.py index cc95317..c307b09 100644 --- a/app/api/domain/tests/test_forward_response_model.py +++ b/app/api/domain/tests/test_forward_response_model.py @@ -1,4 +1,8 @@ -from app.api.domain.forward_response_model import Demographics, ForwardResponse +from app.api.domain.forward_response_model import ( + Demographics, + ForwardResponse, + Permissions, +) def test_forward_response() -> None: @@ -7,6 +11,21 @@ def test_forward_response() -> None: ForwardResponse( sessionId="some session id", supplier="some supplier", - proxy=Demographics(firstName="Betty", surname="Jones", title="Ms"), - patients=[Demographics(firstName="John", surname="Jones", title="Mr")], + odsCode="some ods code", + user=Demographics( + firstName="Betty", + surname="Jones", + title="Ms", + dateOfBirth="12/03/1965", + permissions=Permissions(), + ), + patients=[ + Demographics( + firstName="John", + surname="Jones", + title="Mr", + dateOfBirth="18/05/1966", + permissions=Permissions(), + ), + ], ) diff --git a/app/api/infrastructure/emis/client.py b/app/api/infrastructure/emis/client.py index 9ff6a60..c4011e9 100644 --- a/app/api/infrastructure/emis/client.py +++ b/app/api/infrastructure/emis/client.py @@ -10,12 +10,11 @@ InvalidValueError, NotFoundError, ) -from app.api.domain.forward_response_model import Demographics from app.api.infrastructure.emis.models import ( + EffectiveServices, Identifier, MedicalRecordPermissions, Patient, - Permissions, SessionRequestData, SessionRequestHeaders, SessionResponse, @@ -96,16 +95,45 @@ def transform_response(self, response: dict) -> SessionResponse: Returns: SessionResponse: Homogenised response with other clients """ + # UserPatientLinks relating the user to their patient details + user_self_links = [ + patient_link + for patient_link in response.get("UserPatientLinks", []) + if patient_link.get("AssociationType") == "Self" + ] + + # UserPatientLinks relating the user to patients they can act on behalf of + user_patient_links = [ + patient_link + for patient_link in response.get("UserPatientLinks", []) + if patient_link.get("AssociationType") == "Proxy" + ] + return SessionResponse( sessionId=response.get("SessionId"), endUserSessionId=response.get("EndUserSessionId"), supplier=self.supplier, - proxy=Demographics( + odsCode=self.request.patient_ods_code, + user=Patient( firstName=response.get("FirstName"), surname=response.get("Surname"), title=response.get("Title"), + dateOfBirth=user_self_links[0].get("DateOfBirth") + if user_self_links + else None, + userPatientLinkToken=user_self_links[0].get("UserPatientLinkToken") + if user_self_links + else None, + patientIdentifiers=self._parse_identifiers( + response.get("UserPatientIdentifiers", []) + ), + permissions=self._parse_permissions( + user_self_links[0].get("EffectiveServices", {}) + if user_self_links + else {} + ), ), - patients=self._parse_patients(response), + patients=self._parse_patients(user_patient_links), ) def _mock_response(self) -> dict: @@ -117,17 +145,15 @@ def _mock_response(self) -> dict: with Path((BASE_DIR) / "data" / "mocked_response.json").open("r") as f: return load(f) - def _parse_patients(self, data: dict) -> list[Patient]: + def _parse_patients(self, patient_links: list) -> list[Patient]: """Parsing raw data from Client into structual model. Args: - data (dict): Raw data containing information about multiple patients + patient_links (dict): Raw data containing information about patients Returns: - list[Patient]: Parsed information about multiple patients + list[Patient]: Parsed information about patients """ - # Extra Patient data - patient_links = data.get("UserPatientLinks", []) parsed_patients = [] for patient in patient_links: raw_permissions = patient.get("EffectiveServices", {}) @@ -136,13 +162,18 @@ def _parse_patients(self, data: dict) -> list[Patient]: firstName=patient.get("FirstName"), surname=patient.get("Surname"), title=patient.get("Title"), + dateOfBirth=patient.get("DateOfBirth"), + userPatientLinkToken=patient.get("UserPatientLinkToken"), + patientIdentifiers=self._parse_identifiers( + patient.get("PatientIdentifiers", []) + ), permissions=self._parse_permissions(raw_permissions), ) ) return parsed_patients - def _parse_permissions(self, raw_permissions: dict) -> Permissions: - return Permissions( + def _parse_permissions(self, raw_permissions: dict) -> EffectiveServices: + return EffectiveServices( appointmentsEnabled=raw_permissions.get("AppointmentsEnabled"), demographicsUpdateEnabled=raw_permissions.get("DemographicsUpdateEnabled"), epsEnabled=raw_permissions.get("EpsEnabled"), @@ -181,3 +212,12 @@ def _parse_permissions(self, raw_permissions: dict) -> Permissions: ), ), ) + + def _parse_identifiers(self, raw_identifiers: list) -> list[Identifier]: + return [ + Identifier( + value=identifier.get("IdentifierValue"), + type=identifier.get("IdentifierType"), + ) + for identifier in raw_identifiers + ] diff --git a/app/api/infrastructure/emis/models.py b/app/api/infrastructure/emis/models.py index a6a746d..b2cc49c 100644 --- a/app/api/infrastructure/emis/models.py +++ b/app/api/infrastructure/emis/models.py @@ -1,7 +1,11 @@ from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_camel -from app.api.domain.forward_response_model import Demographics, ForwardResponse +from app.api.domain.forward_response_model import ( + Demographics, + ForwardResponse, + Permissions, +) class Identifier(BaseModel): @@ -62,7 +66,7 @@ class MedicalRecordPermissions(BaseModel): test_results_enabled: bool -class Permissions(BaseModel): +class EffectiveServices(Permissions): """Base Model for Permissions.""" model_config = ConfigDict(alias_generator=to_camel) @@ -80,9 +84,13 @@ class Permissions(BaseModel): class Patient(Demographics): - """Base Model for Patient.""" + """Base Model for User and Patient.""" - permissions: Permissions + model_config = ConfigDict(alias_generator=to_camel) + + user_patient_link_token: str + patient_identifiers: list[Identifier] + permissions: EffectiveServices class SessionResponse(ForwardResponse): @@ -91,4 +99,5 @@ class SessionResponse(ForwardResponse): model_config = ConfigDict(alias_generator=to_camel) end_user_session_id: str + user: Patient patients: list[Patient] diff --git a/app/api/infrastructure/emis/tests/test_client.py b/app/api/infrastructure/emis/tests/test_client.py index 2ff7e51..8accad9 100644 --- a/app/api/infrastructure/emis/tests/test_client.py +++ b/app/api/infrastructure/emis/tests/test_client.py @@ -13,12 +13,12 @@ NotFoundError, ) from app.api.domain.forward_request_model import ForwardRequest -from app.api.domain.forward_response_model import Demographics from app.api.infrastructure.emis.client import EmisClient from app.api.infrastructure.emis.models import ( + EffectiveServices, + Identifier, MedicalRecordPermissions, Patient, - Permissions, SessionResponse, ) @@ -139,39 +139,47 @@ def test_emis_client_transform_response(client: EmisClient) -> None: sessionId="SID_2qZ9yJpVxHq4N3b", endUserSessionId="SESS_mDq6nE2b8R7KQ0v", supplier="EMIS", - proxy=Demographics(firstName="Alex", surname="Taylor", title="Mr"), - patients=[ - Patient( - firstName="Alex", - surname="Taylor", - title="Mr", - permissions=Permissions( - appointmentsEnabled=True, - demographicsUpdateEnabled=True, - epsEnabled=True, - medicalRecordEnabled=True, - onlineTriageEnabled=False, - practicePatientCommunicationEnabled=False, - prescribingEnabled=True, - recordSharingEnabled=False, - recordViewAuditEnabled=True, - medicalRecord=MedicalRecordPermissions( - recordAccessScheme="DetailedCodedCareRecord", - allergiesEnabled=True, - consultationsEnabled=True, - immunisationsEnabled=True, - documentsEnabled=True, - medicationEnabled=True, - problemsEnabled=True, - testResultsEnabled=True, - ), + odsCode="some patient ods code", + user=Patient( + firstName="Alex", + surname="Taylor", + title="Mr", + dateOfBirth="1985-06-25", + userPatientLinkToken="link_self_9aLw3G7kVQ", + patientIdentifiers=[Identifier(value="9434765919", type="NhsNumber")], + permissions=EffectiveServices( + appointmentsEnabled=True, + demographicsUpdateEnabled=True, + epsEnabled=True, + medicalRecordEnabled=True, + onlineTriageEnabled=False, + practicePatientCommunicationEnabled=False, + prescribingEnabled=True, + recordSharingEnabled=False, + recordViewAuditEnabled=True, + medicalRecord=MedicalRecordPermissions( + recordAccessScheme="DetailedCodedCareRecord", + allergiesEnabled=True, + consultationsEnabled=True, + immunisationsEnabled=True, + documentsEnabled=True, + medicationEnabled=True, + problemsEnabled=True, + testResultsEnabled=True, ), ), + ), + patients=[ Patient( firstName="Jane", surname="Doe", title="Mrs", - permissions=Permissions( + dateOfBirth="1979-01-15", + userPatientLinkToken="link_proxy_jane_5QJw7r2m", + patientIdentifiers=[ + Identifier(value="2222222222", type="NhsNumber"), + ], + permissions=EffectiveServices( appointmentsEnabled=False, demographicsUpdateEnabled=True, epsEnabled=False, @@ -197,7 +205,12 @@ def test_emis_client_transform_response(client: EmisClient) -> None: firstName="Ella", surname="Taylor", title="Ms", - permissions=Permissions( + dateOfBirth="2010-03-02", + userPatientLinkToken="link_proxy_ella_Z01r8yPa", + patientIdentifiers=[ + Identifier(value="3333333333", type="NhsNumber"), + ], + permissions=EffectiveServices( appointmentsEnabled=True, demographicsUpdateEnabled=True, epsEnabled=False, @@ -224,44 +237,56 @@ def test_emis_client_transform_response(client: EmisClient) -> None: @pytest.mark.parametrize( - "response", + ("response", "expected_error"), [ - {}, - { # Missing UserPatientLints - "SessionId": "some session", - "FirstName": "someone's first name", - "Surname": "someone's surname", - "Title": "someone's title", - }, - { # Missing Proxy Demographic information - "SessionId": "some session", - "UserPatientLinks": [ - { - "FirstName": "someone's first name", - "Surname": "someone's surname", - "Title": "someone's title", - } - ], - }, - { # Missing Session Id - "FirstName": "someone's first name", - "Surname": "someone's surname", - "Title": "someone's title", - "UserPatientLinks": [ - { - "FirstName": "someone's first name", - "Surname": "someone's surname", - "Title": "someone's title", - } - ], - }, + ({}, ValueError), + ( + { # Missing UserPatientLints + "SessionId": "some session", + "FirstName": "someone's first name", + "Surname": "someone's surname", + "Title": "someone's title", + }, + ValueError, + ), + ( + { # Missing Proxy Demographic information + "SessionId": "some session", + "UserPatientLinks": [ + { + "FirstName": "someone's first name", + "Surname": "someone's surname", + "Title": "someone's title", + "AssociationType": "Self", + } + ], + }, + ValidationError, + ), + ( + { # Missing Session Id + "FirstName": "someone's first name", + "Surname": "someone's surname", + "Title": "someone's title", + "UserPatientLinks": [ + { + "FirstName": "someone's first name", + "Surname": "someone's surname", + "Title": "someone's title", + "AssociationType": "Self", + } + ], + }, + ValidationError, + ), ], ) -def test_emis_client_transform_response_raise_validation_error( +def test_emis_client_transform_response_raise_error( response: dict, + expected_error: Exception, client: EmisClient, ) -> None: """Test the EmisClient transform_response function raises validation error.""" # Act & Assert - with pytest.raises(ValidationError): + with pytest.raises(expected_error): client.transform_response(response) diff --git a/app/api/infrastructure/tpp/client.py b/app/api/infrastructure/tpp/client.py index 6869e02..88f6760 100644 --- a/app/api/infrastructure/tpp/client.py +++ b/app/api/infrastructure/tpp/client.py @@ -10,11 +10,11 @@ InvalidValueError, NotFoundError, ) -from app.api.domain.forward_response_model import Demographics, ForwardResponse +from app.api.domain.forward_response_model import ForwardResponse from app.api.infrastructure.tpp.models import ( Application, Identifier, - Patient, + Person, ServiceAccess, ServiceAccessDescription, ServiceAccessStatus, @@ -99,15 +99,26 @@ def transform_response(self, response: dict) -> ForwardResponse: ForwardResponse: Homogenised response with other clients """ response = response.get("CreateSessionReply", {}) - proxy_link = response.get("User", {}) - proxy_person = proxy_link.get("Person", {}) + user_link = response.get("User", {}) + user_person = user_link.get("Person", {}) + return SessionResponse( sessionId=response.get("@suid"), supplier=self.supplier, - proxy=Demographics( - firstName=proxy_person.get("PersonName", {}).get("@firstName"), - surname=proxy_person.get("PersonName", {}).get("@surname"), - title=proxy_person.get("PersonName", {}).get("@title"), + odsCode=self.request.patient_ods_code, + onlineUserId=user_link.get("@onlineUserId"), + user=Person( + firstName=user_person.get("PersonName", {}).get("@firstName"), + surname=user_person.get("PersonName", {}).get("@surname"), + title=user_person.get("PersonName", {}).get("@title"), + dateOfBirth=user_person.get("@dateOfBirth"), + patientId=user_person.get("@patientId"), + patientIdentifiers=self._parse_identifiers( + user_person.get("NationalIdentifiers", []) + ), + permissions=self._parse_permissions( + user_person.get("EffectiveServiceAccess", []) + ), ), patients=self._parse_patients(response), ) @@ -124,7 +135,7 @@ def _mock_response(self) -> dict: mocked_response = f.read() return xmltodict.parse(mocked_response) - def _parse_patients(self, data: dict) -> list[Patient]: + def _parse_patients(self, data: dict) -> list[Person]: """Parsing raw data from Client into structual model. Args: @@ -143,20 +154,37 @@ def _parse_patients(self, data: dict) -> list[Patient]: parsed_patients = [] for patient in patient_links: person = patient["Person"] - raw_permissions = person.get("EffectiveServiceAccess", []).get( - "ServiceAccess", [] - ) + raw_permissions = person.get("EffectiveServiceAccess", []) parsed_patients.append( - Patient( + Person( firstName=person.get("PersonName", {}).get("@firstName"), surname=person.get("PersonName", {}).get("@surname"), title=person.get("PersonName", {}).get("@title"), + dateOfBirth=person.get("@dateOfBirth"), + patientId=person.get("@patientId"), + patientIdentifiers=self._parse_identifiers( + person.get("NationalIdentifiers", []) + ), permissions=self._parse_permissions(raw_permissions), ) ) return parsed_patients - def _parse_permissions(self, raw_permissions: dict) -> list[ServiceAccess]: + def _parse_permissions(self, raw_permissions: dict | list) -> list[ServiceAccess]: + if not raw_permissions: + return [] + # xmltodict gives us {"ServiceAccess": {...}} for one element and + # {"ServiceAccess": [{...}, {...}]} for multiple — extract the inner value first + service_access = ( + raw_permissions.get("ServiceAccess") + if isinstance(raw_permissions, dict) + else [] + ) + if not service_access: + return [] + if isinstance(service_access, dict): + # Single element — normalise to a list + service_access = [service_access] return [ ServiceAccess( description=ServiceAccessDescription(service["@description"]), @@ -166,5 +194,28 @@ def _parse_permissions(self, raw_permissions: dict) -> list[ServiceAccess]: service["@statusDesc"] ), ) - for service in raw_permissions + for service in service_access + ] + + def _parse_identifiers(self, raw_identifiers: dict | list) -> list[Identifier]: + if not raw_identifiers: + return [] + # xmltodict gives us {"Identifier": {...}} for one element and + # {"Identifier": [{...}, {...}]} for multiple — extract the inner value first + identifiers = ( + raw_identifiers.get("Identifier") + if isinstance(raw_identifiers, dict) + else [] + ) + if not identifiers: + return [] + if isinstance(identifiers, dict): + # Single element — normalise to a list + identifiers = [identifiers] + return [ + Identifier( + value=identifier.get("@value"), + type=identifier.get("@type"), + ) + for identifier in identifiers ] diff --git a/app/api/infrastructure/tpp/data/mocked_response.xml b/app/api/infrastructure/tpp/data/mocked_response.xml index bbda5c0..f143191 100644 --- a/app/api/infrastructure/tpp/data/mocked_response.xml +++ b/app/api/infrastructure/tpp/data/mocked_response.xml @@ -9,6 +9,20 @@ + + + + + + + + + + + + + + diff --git a/app/api/infrastructure/tpp/models.py b/app/api/infrastructure/tpp/models.py index 8727115..c982ad2 100644 --- a/app/api/infrastructure/tpp/models.py +++ b/app/api/infrastructure/tpp/models.py @@ -4,7 +4,11 @@ from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_camel -from app.api.domain.forward_response_model import Demographics, ForwardResponse +from app.api.domain.forward_response_model import ( + Demographics, + ForwardResponse, + Permissions, +) class Application(BaseModel): @@ -102,7 +106,7 @@ class ServiceAccessStatusDescription(Enum): OTHER = "Other" -class ServiceAccess(BaseModel): +class ServiceAccess(Permissions): """Base Model for Service Access which holds data per permission.""" model_config = ConfigDict(alias_generator=to_camel) @@ -113,9 +117,13 @@ class ServiceAccess(BaseModel): status_description: ServiceAccessStatusDescription -class Patient(Demographics): - """Base Model for Patient.""" +class Person(Demographics): + """Base Model for User and Patient.""" + model_config = ConfigDict(alias_generator=to_camel) + + patient_id: str | None # Not necessary for the user in cross practice proxy roles + patient_identifiers: list[Identifier] permissions: list[ServiceAccess] @@ -124,4 +132,6 @@ class SessionResponse(ForwardResponse): model_config = ConfigDict(alias_generator=to_camel) - patients: list[Patient] + online_user_id: str + user: Person + patients: list[Person] diff --git a/app/api/infrastructure/tpp/tests/test_client.py b/app/api/infrastructure/tpp/tests/test_client.py index d91dbea..88db7d7 100644 --- a/app/api/infrastructure/tpp/tests/test_client.py +++ b/app/api/infrastructure/tpp/tests/test_client.py @@ -13,10 +13,10 @@ NotFoundError, ) from app.api.domain.forward_request_model import ForwardRequest -from app.api.domain.forward_response_model import Demographics from app.api.infrastructure.tpp.client import TPPClient from app.api.infrastructure.tpp.models import ( - Patient, + Identifier, + Person, ServiceAccess, ServiceAccessDescription, ServiceAccessStatus, @@ -145,12 +145,111 @@ def test_tpp_client_transform_response(client: TPPClient) -> None: assert actual_result == SessionResponse( sessionId="xhvE9/jCjdafytcXBq8LMKMgc4wA/w5k/O5C4ip0Fs9GPbIQ/WRIZi4Och1Spmg7aYJR2CZVLHfu6cRVv84aEVrRE8xahJbT4TPAr8N/CYix6TBquQsZibYXYMxJktXcYKwDhBH8yr3iJYnyevP3hV76oTjVmKieBtYzSSZAOu4=", supplier="TPP", - proxy=Demographics(firstName="Sam", surname="Jones", title="Mr"), + odsCode="some patient ods code", + onlineUserId="9cbf400000000000", + user=Person( + firstName="Sam", + surname="Jones", + title="Mr", + dateOfBirth="1990-11-05", + permissions=[ + ServiceAccess( + description=ServiceAccessDescription("Full Clinical Record"), + serviceIdentifier=1, + status=ServiceAccessStatus("U"), + statusDescription=ServiceAccessStatusDescription("Unavailable"), + ), + ServiceAccess( + serviceIdentifier=2, + description=ServiceAccessDescription("Appointments"), + status=ServiceAccessStatus("A"), + statusDescription=ServiceAccessStatusDescription("Available"), + ), + ServiceAccess( + serviceIdentifier=4, + description=ServiceAccessDescription("Request Medication"), + status=ServiceAccessStatus("A"), + statusDescription=ServiceAccessStatusDescription("Available"), + ), + ServiceAccess( + serviceIdentifier=8, + description=ServiceAccessDescription("Questionnaires"), + status=ServiceAccessStatus("N"), + statusDescription=ServiceAccessStatusDescription( + "Not offered by unit" + ), + ), + ServiceAccess( + serviceIdentifier=64, + description=ServiceAccessDescription("Summary Record"), + status=ServiceAccessStatus("A"), + statusDescription=ServiceAccessStatusDescription("Available"), + ), + ServiceAccess( + serviceIdentifier=128, + description=ServiceAccessDescription("Detailed Coded Record"), + status=ServiceAccessStatus("U"), + statusDescription=ServiceAccessStatusDescription("Unavailable"), + ), + ServiceAccess( + serviceIdentifier=512, + description=ServiceAccessDescription("Messaging"), + status=ServiceAccessStatus("A"), + statusDescription=ServiceAccessStatusDescription("Available"), + ), + ServiceAccess( + serviceIdentifier=1024, + description=ServiceAccessDescription("View Sharing Status"), + status=ServiceAccessStatus("N"), + statusDescription=ServiceAccessStatusDescription( + "Not offered by unit" + ), + ), + ServiceAccess( + serviceIdentifier=2048, + description=ServiceAccessDescription("Record Audit"), + status=ServiceAccessStatus("A"), + statusDescription=ServiceAccessStatusDescription("Available"), + ), + ServiceAccess( + serviceIdentifier=4096, + description=ServiceAccessDescription("Change Pharmacy"), + status=ServiceAccessStatus("N"), + statusDescription=ServiceAccessStatusDescription( + "Not offered by unit" + ), + ), + ServiceAccess( + serviceIdentifier=8192, + description=ServiceAccessDescription( + "Manage Sharing Rules And Requests" + ), + status=ServiceAccessStatus("G"), + statusDescription=ServiceAccessStatusDescription( + "Only available to GMS registered patients" + ), + ), + ServiceAccess( + serviceIdentifier=65536, + description=ServiceAccessDescription("Access SystmConnect"), + status=ServiceAccessStatus("O"), + statusDescription=ServiceAccessStatusDescription("Other"), + ), + ], + patientId=None, + patientIdentifiers=[ + Identifier( + value="1111111111", + type="NhsNumber", + ) + ], + ), patients=[ - Patient( + Person( firstName="Clare", surname="Jones", title="Mrs", + dateOfBirth="1975-04-21", permissions=[ ServiceAccess( description=ServiceAccessDescription("Full Clinical Record"), @@ -235,6 +334,13 @@ def test_tpp_client_transform_response(client: TPPClient) -> None: statusDescription=ServiceAccessStatusDescription("Other"), ), ], + patientId="82f3500000000000", + patientIdentifiers=[ + Identifier( + value="2222222222", + type="NhsNumber", + ) + ], ) ], ) diff --git a/specification/im1-pfs-auth-api.yaml b/specification/im1-pfs-auth-api.yaml index ad9f6db..894d255 100644 --- a/specification/im1-pfs-auth-api.yaml +++ b/specification/im1-pfs-auth-api.yaml @@ -210,10 +210,11 @@ components: EMISResponseModel: required: - sessionId - endUserSessionId - supplier - proxy - patients + - endUserSessionId + - supplier + - odsCode + - user + - patients type: object properties: sessionId: @@ -224,20 +225,24 @@ components: supplier: enum: [EMIS] type: string - proxy: - $ref: "#/components/schemas/DemographicsModel" + odsCode: + type: string + user: + $ref: "#/components/schemas/EMISPersonModel" patients: type: array items: - $ref: "#/components/schemas/EMISPatientModel" + $ref: "#/components/schemas/EMISPersonModel" additionalProperties: false TPPResponseModel: required: - sessionId - supplier - proxy - patients + - supplier + - odsCode + - onlineUserId + - user + - patients type: object properties: sessionId: @@ -246,19 +251,24 @@ components: supplier: enum: [TPP] type: string - proxy: - $ref: "#/components/schemas/DemographicsModel" + odsCode: + type: string + onlineUserId: + type: string + user: + $ref: "#/components/schemas/TPPPersonModel" patients: type: array items: - $ref: "#/components/schemas/TPPPatientModel" + $ref: "#/components/schemas/TPPPersonModel" additionalProperties: false DemographicsModel: required: - firstName - surname - title + - surname + - title + - dateOfBirth type: object properties: firstName: @@ -267,29 +277,50 @@ components: type: string title: type: string + dateOfBirth: + type: string + + PatientIdentifierModel: + required: + - value + - type + type: object + properties: + value: + type: string + type: + type: string - EMISPatientModel: + EMISPersonModel: allOf: - $ref: "#/components/schemas/DemographicsModel" - required: + - userPatientLinkToken + - patientIdentifiers - permissions type: object properties: + userPatientLinkToken: + type: string + patientIdentifiers: + type: array + items: + $ref: "#/components/schemas/PatientIdentifierModel" permissions: $ref: "#/components/schemas/EMISPermissionsModel" EMISPermissionsModel: required: - appointmentsEnabled - demographicsUpdateEnabled - epsEnabled - medicalRecordEnabled - onlineTriageEnabled - practicePatientCommunicationEnabled - prescribingEnabled - recordSharingEnabled - recordViewAuditEnabled - medicalRecord + - demographicsUpdateEnabled + - epsEnabled + - medicalRecordEnabled + - onlineTriageEnabled + - practicePatientCommunicationEnabled + - prescribingEnabled + - recordSharingEnabled + - recordViewAuditEnabled + - medicalRecord type: object properties: appointmentsEnabled: @@ -316,13 +347,13 @@ components: EMISMedicalRecordModel: required: - recordAccessScheme - allergiesEnabled - consultationsEnabled - immunisationsEnabled - documentsEnabled - medicationEnabled - problemsEnabled - testResultsEnabled + - allergiesEnabled + - consultationsEnabled + - immunisationsEnabled + - documentsEnabled + - medicationEnabled + - problemsEnabled + - testResultsEnabled type: object properties: recordAccessScheme: @@ -349,24 +380,33 @@ components: testResultsEnabled: type: boolean - TPPPatientModel: + TPPPersonModel: allOf: - $ref: "#/components/schemas/DemographicsModel" - required: + - patientId + - patientIdentifiers - permissions type: object properties: + patientId: + type: string + nullable: true + patientIdentifiers: + type: array + items: + $ref: "#/components/schemas/PatientIdentifierModel" permissions: type: array items: - $ref: "#/components/schemas/TPPServiceAccess" + $ref: "#/components/schemas/TPPPermissionsModel" - TPPServiceAccess: + TPPPermissionsModel: required: - description - serviceIdentifier - status - statusDescription + - serviceIdentifier + - status + - statusDescription type: object properties: description: diff --git a/tests/end_to_end/authenticate/POST/test_201.py b/tests/end_to_end/authenticate/POST/test_201.py index 506ef4a..7ad46a8 100644 --- a/tests/end_to_end/authenticate/POST/test_201.py +++ b/tests/end_to_end/authenticate/POST/test_201.py @@ -18,37 +18,51 @@ ( "https://nhs70apptest.emishealth.com", { - "patients": [ - { - "firstName": "Alex", - "surname": "Taylor", - "title": "Mr", - "permissions": { - "appointmentsEnabled": True, - "demographicsUpdateEnabled": True, - "epsEnabled": True, - "medicalRecordEnabled": True, - "onlineTriageEnabled": False, - "practicePatientCommunicationEnabled": False, - "prescribingEnabled": True, - "recordSharingEnabled": False, - "recordViewAuditEnabled": True, - "medicalRecord": { - "recordAccessScheme": "DetailedCodedCareRecord", - "allergiesEnabled": True, - "consultationsEnabled": True, - "immunisationsEnabled": True, - "documentsEnabled": True, - "medicationEnabled": True, - "problemsEnabled": True, - "testResultsEnabled": True, - }, + "sessionId": "SID_2qZ9yJpVxHq4N3b", + "endUserSessionId": "SESS_mDq6nE2b8R7KQ0v", + "supplier": "EMIS", + "odsCode": "ODS123", + "user": { + "firstName": "Alex", + "surname": "Taylor", + "title": "Mr", + "dateOfBirth": "1985-06-25", + "userPatientLinkToken": "link_self_9aLw3G7kVQ", + "patientIdentifiers": [ + {"value": "9434765919", "type": "NhsNumber"}, + ], + "permissions": { + "appointmentsEnabled": True, + "demographicsUpdateEnabled": True, + "epsEnabled": True, + "medicalRecordEnabled": True, + "onlineTriageEnabled": False, + "practicePatientCommunicationEnabled": False, + "prescribingEnabled": True, + "recordSharingEnabled": False, + "recordViewAuditEnabled": True, + "medicalRecord": { + "recordAccessScheme": "DetailedCodedCareRecord", + "allergiesEnabled": True, + "consultationsEnabled": True, + "immunisationsEnabled": True, + "documentsEnabled": True, + "medicationEnabled": True, + "problemsEnabled": True, + "testResultsEnabled": True, }, }, + }, + "patients": [ { "firstName": "Jane", "surname": "Doe", "title": "Mrs", + "dateOfBirth": "1979-01-15", + "userPatientLinkToken": "link_proxy_jane_5QJw7r2m", + "patientIdentifiers": [ + {"value": "2222222222", "type": "NhsNumber"}, + ], "permissions": { "appointmentsEnabled": False, "demographicsUpdateEnabled": True, @@ -75,6 +89,11 @@ "firstName": "Ella", "surname": "Taylor", "title": "Ms", + "dateOfBirth": "2010-03-02", + "userPatientLinkToken": "link_proxy_ella_Z01r8yPa", + "patientIdentifiers": [ + {"value": "3333333333", "type": "NhsNumber"}, + ], "permissions": { "appointmentsEnabled": True, "demographicsUpdateEnabled": True, @@ -98,20 +117,111 @@ }, }, ], - "proxy": {"firstName": "Alex", "surname": "Taylor", "title": "Mr"}, - "sessionId": "SID_2qZ9yJpVxHq4N3b", - "endUserSessionId": "SESS_mDq6nE2b8R7KQ0v", - "supplier": "EMIS", }, ), ( "https://systmonline2.tpp-uk.com", { + "sessionId": "xhvE9/jCjdafytcXBq8LMKMgc4wA/w5k/O5C4ip0Fs9GPbIQ/WRIZi4Och1Spmg7aYJR2CZVLHfu6cRVv84aEVrRE8xahJbT4TPAr8N/CYix6TBquQsZibYXYMxJktXcYKwDhBH8yr3iJYnyevP3hV76oTjVmKieBtYzSSZAOu4=", # noqa: E501 + "supplier": "TPP", + "odsCode": "ODS123", + "onlineUserId": "9cbf400000000000", + "user": { + "firstName": "Sam", + "surname": "Jones", + "title": "Mr", + "dateOfBirth": "1990-11-05", + "patientId": None, + "patientIdentifiers": [ + {"value": "1111111111", "type": "NhsNumber"}, + ], + "permissions": [ + { + "description": "Full Clinical Record", + "statusDescription": "Unavailable", + "serviceIdentifier": 1, + "status": "U", + }, + { + "serviceIdentifier": 2, + "statusDescription": "Available", + "description": "Appointments", + "status": "A", + }, + { + "serviceIdentifier": 4, + "statusDescription": "Available", + "description": "Request Medication", + "status": "A", + }, + { + "serviceIdentifier": 8, + "description": "Questionnaires", + "status": "N", + "statusDescription": "Not offered by unit", + }, + { + "serviceIdentifier": 64, + "statusDescription": "Available", + "description": "Summary Record", + "status": "A", + }, + { + "serviceIdentifier": 128, + "statusDescription": "Unavailable", + "description": "Detailed Coded Record", + "status": "U", + }, + { + "serviceIdentifier": 512, + "statusDescription": "Available", + "description": "Messaging", + "status": "A", + }, + { + "serviceIdentifier": 1024, + "statusDescription": "Not offered by unit", + "description": "View Sharing Status", + "status": "N", + }, + { + "serviceIdentifier": 2048, + "statusDescription": "Available", + "description": "Record Audit", + "status": "A", + }, + { + "serviceIdentifier": 4096, + "statusDescription": "Not offered by unit", + "description": "Change Pharmacy", + "status": "N", + }, + { + "serviceIdentifier": 8192, + "statusDescription": ( + "Only available to GMS registered patients" + ), + "description": "Manage Sharing Rules And Requests", + "status": "G", + }, + { + "serviceIdentifier": 65536, + "statusDescription": "Other", + "description": "Access SystmConnect", + "status": "O", + }, + ], + }, "patients": [ { "firstName": "Clare", "surname": "Jones", "title": "Mrs", + "dateOfBirth": "1975-04-21", + "patientId": "82f3500000000000", + "patientIdentifiers": [ + {"value": "2222222222", "type": "NhsNumber"}, + ], "permissions": [ { "description": "Full Clinical Record", @@ -190,9 +300,6 @@ ], }, ], - "proxy": {"firstName": "Sam", "surname": "Jones", "title": "Mr"}, - "sessionId": "xhvE9/jCjdafytcXBq8LMKMgc4wA/w5k/O5C4ip0Fs9GPbIQ/WRIZi4Och1Spmg7aYJR2CZVLHfu6cRVv84aEVrRE8xahJbT4TPAr8N/CYix6TBquQsZibYXYMxJktXcYKwDhBH8yr3iJYnyevP3hV76oTjVmKieBtYzSSZAOu4=", # noqa: E501 - "supplier": "TPP", }, ), ],