Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use generic error message for server error #324

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 59 additions & 106 deletions ack_backend/tests/test_ack_processor.py

Large diffs are not rendered by default.

109 changes: 97 additions & 12 deletions ack_backend/tests/test_utils_for_ack_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

from datetime import datetime

SOURCE_BUCKET_NAME = "immunisation-batch-internal-test-data-sources"
DESTINATION_BUCKET_NAME = "immunisation-batch-internal-test-data-destinations"
CONFIG_BUCKET_NAME = "immunisation-batch-internal-dev-configs"
STREAM_NAME = "imms-batch-internal-dev-processingdata-stream"
CREATED_AT_FORMATTED_STRING = "20241115T13435500"
MOCK_CREATED_AT_FORMATTED_STRING = "20211120T12000000"
IMMS_ID = "Immunization#932796c8-fd20-4d31-a4d7-e9613de70ad6"


AWS_REGION = "eu-west-2"
REGION_NAME = "eu-west-2"
STATIC_DATETIME = datetime(2021, 11, 20, 12, 0, 0)


class BucketNames:
"""Bucket Names for testing"""

DESTINATION = "immunisation-batch-internal-test-data-destinations"


MOCK_ENVIRONMENT_DICT = {"ACK_BUCKET_NAME": BucketNames.DESTINATION}


class DiagnosticsDictionaries:
"""Example diagnostics dictionaries which may be received from the record forwarder"""

Expand Down Expand Up @@ -163,7 +169,7 @@ class ValidValues:
"RESPONSE_TYPE": "Business",
"RESPONSE_CODE": "30001",
"RESPONSE_DISPLAY": "Success",
"RECEIVED_TIME": CREATED_AT_FORMATTED_STRING,
"RECEIVED_TIME": MOCK_CREATED_AT_FORMATTED_STRING,
"MAILBOX_FROM": "",
"LOCAL_ID": local_id,
"IMMS_ID": "",
Expand All @@ -180,7 +186,7 @@ class ValidValues:
"RESPONSE_TYPE": "Business",
"RESPONSE_CODE": "30002",
"RESPONSE_DISPLAY": "Business Level Response Value - Processing Error",
"RECEIVED_TIME": CREATED_AT_FORMATTED_STRING,
"RECEIVED_TIME": MOCK_CREATED_AT_FORMATTED_STRING,
"MAILBOX_FROM": "",
"LOCAL_ID": local_id,
"IMMS_ID": "",
Expand All @@ -189,29 +195,29 @@ class ValidValues:
}

update_ack_file_successful_row_no_immsid = (
f"123^1|OK|Information|OK|30001|Business|30001|Success|{CREATED_AT_FORMATTED_STRING}||{local_id}|||True\n"
f"123^1|OK|Information|OK|30001|Business|30001|Success|{MOCK_CREATED_AT_FORMATTED_STRING}||{local_id}|||True\n"
)

update_ack_file_failure_row_no_immsid = (
"123^1|Fatal Error|Fatal|Fatal Error|30002|Business|30002|"
f"Business Level Response Value - Processing Error|{CREATED_AT_FORMATTED_STRING}|"
f"Business Level Response Value - Processing Error|{MOCK_CREATED_AT_FORMATTED_STRING}|"
f"|{local_id}||Error_value|False\n"
)

update_ack_file_successful_row_immsid = (
"123^1|OK|Information|OK|30001|Business|30001|Success"
f"|{CREATED_AT_FORMATTED_STRING}||{local_id}|{imms_id}||True\n"
f"|{MOCK_CREATED_AT_FORMATTED_STRING}||{local_id}|{imms_id}||True\n"
)

update_ack_file_failure_row_immsid = (
"123^1|Fatal Error|Fatal|Fatal Error|30002|Business|30002|Business Level Response Value - Processing Error"
f"|{CREATED_AT_FORMATTED_STRING}||{local_id}|{imms_id}|Error_value|False\n"
f"|{MOCK_CREATED_AT_FORMATTED_STRING}||{local_id}|{imms_id}|Error_value|False\n"
)

existing_ack_file_content = (
"MESSAGE_HEADER_ID|HEADER_RESPONSE_CODE|ISSUE_SEVERITY|ISSUE_CODE|ISSUE_DETAILS_CODE|RESPONSE_TYPE|"
"RESPONSE_CODE|RESPONSE_DISPLAY|RECEIVED_TIME|MAILBOX_FROM|LOCAL_ID|IMMS_ID|OPERATION_OUTCOME"
"|MESSAGE_DELIVERY\n123^5|OK|Information|OK|30001|Business|30001|Success|20241115T13435500||999^TEST|||True\n"
f"|MESSAGE_DELIVERY\n123^5|OK|Information|OK|30001|Business|30001|Success|{MOCK_CREATED_AT_FORMATTED_STRING}||999^TEST|||True\n"
)

test_ack_header = (
Expand Down Expand Up @@ -239,3 +245,82 @@ class InvalidValues:
"statusCode": 500,
"diagnostics": "An unhandled error occurred during batch processing",
}


class GenericSetUp:
"""
Performs generic setup of mock resources:
* If s3_client is provided, creates source, destination and firehose buckets (firehose bucket is used for testing
only)
* If firehose_client is provided, creates a firehose delivery stream
"""

def __init__(self, s3_client=None, firehose_client=None):

if s3_client:
for bucket_name in [BucketNames.DESTINATION]:
s3_client.create_bucket(
Bucket=bucket_name, CreateBucketConfiguration={"LocationConstraint": REGION_NAME}
)

# if firehose_client:
# firehose_client.create_delivery_stream(
# DeliveryStreamName=Firehose.STREAM_NAME,
# DeliveryStreamType="DirectPut",
# S3DestinationConfiguration={
# "RoleARN": "arn:aws:iam::123456789012:role/mock-role",
# "BucketARN": "arn:aws:s3:::" + BucketNames.MOCK_FIREHOSE,
# "Prefix": "firehose-backup/",
# },
# )


class GenericTearDown:
"""Performs generic tear down of mock resources"""

def __init__(self, s3_client=None, firehose_client=None, kinesis_client=None):

if s3_client:
for bucket_name in [BucketNames.DESTINATION]:
for obj in s3_client.list_objects_v2(Bucket=bucket_name).get("Contents", []):
s3_client.delete_object(Bucket=bucket_name, Key=obj["Key"])
s3_client.delete_bucket(Bucket=bucket_name)

# if firehose_client:
# firehose_client.delete_delivery_stream(DeliveryStreamName=Firehose.STREAM_NAME)


class FileDetails:
"""
Class to create and hold values for a mock file, based on the vaccine type, supplier and ods code.
NOTE: Supplier and ODS code are hardcoded rather than mapped, for testing purposes.
NOTE: The permissions_list and permissions_config are examples of full permissions for the suppler for the
vaccine type.
"""

def __init__(self, vaccine_type: str, supplier: str, ods_code: str):
self.name = f"{vaccine_type.upper()}/ {supplier.upper()} file"
self.created_at_formatted_string = MOCK_CREATED_AT_FORMATTED_STRING
self.file_key = f"{vaccine_type}_Vaccinations_v5_{ods_code}_20210730T12000000.csv"
self.ack_file_key = (
f"forwardedFile/{vaccine_type}_Vaccinations_v5_{ods_code}_20210730T12000000_BusAck_20211120T12000000.csv"
)
self.vaccine_type = vaccine_type
self.ods_code = ods_code
self.supplier = supplier
self.message_id = f"{vaccine_type.lower()}_{supplier.lower()}_test_id"

self.base_event = {
"file_key": self.file_key,
"supplier": self.supplier,
"vaccine_type": self.vaccine_type,
"created_at_formatted_string": self.created_at_formatted_string,
}


class MockFileDetails:
"""Class containing mock file details for use in tests"""

rsv_ravs = FileDetails("RSV", "RAVS", "X26")
rsv_emis = FileDetails("RSV", "EMIS", "8HK48")
flu_emis = FileDetails("FLU", "EMIS", "YGM41")
3 changes: 3 additions & 0 deletions backend/src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ class Urls:
)
ods_organization_code = "https://fhir.nhs.uk/Id/ods-organization-code"
urn_school_number = "https://fhir.hl7.org.uk/Id/urn-school-number"


GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE = "Unable to process request. Issue may be transient."
8 changes: 6 additions & 2 deletions backend/src/create_imms_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from local_lambda import load_string
from models.errors import Severity, Code, create_operation_outcome
from log_structure import function_info
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE


@function_info
Expand All @@ -17,9 +18,12 @@ def create_imms_handler(event, context):
def create_immunization(event, controller: FhirController):
try:
return controller.create_immunization(event)
except Exception as e:
except Exception: # pylint: disable = broad-exception-caught
exp_error = create_operation_outcome(
resource_id=str(uuid.uuid4()), severity=Severity.error, code=Code.server_error, diagnostics=str(e)
resource_id=str(uuid.uuid4()),
severity=Severity.error,
code=Code.server_error,
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
)
return FhirController.create_response(500, exp_error)

Expand Down
24 changes: 13 additions & 11 deletions backend/src/delete_imms_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from fhir_controller import FhirController, make_controller
from models.errors import Severity, Code, create_operation_outcome
from log_structure import function_info
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE


@function_info
Expand All @@ -16,10 +17,13 @@ def delete_imms_handler(event, context):
def delete_immunization(event, controller: FhirController):
try:
return controller.delete_immunization(event)
except Exception as e:
exp_error = create_operation_outcome(resource_id=str(uuid.uuid4()), severity=Severity.error,
code=Code.server_error,
diagnostics=str(e))
except Exception: # pylint: disable = broad-exception-caught
exp_error = create_operation_outcome(
resource_id=str(uuid.uuid4()),
severity=Severity.error,
code=Code.server_error,
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
)
return FhirController.create_response(500, exp_error)


Expand All @@ -29,13 +33,11 @@ def delete_immunization(event, controller: FhirController):
args = parser.parse_args()

event = {
"pathParameters": {
"id": args.id
},
"pathParameters": {"id": args.id},
"headers": {
'Content-Type': 'application/x-www-form-urlencoded',
'AuthenticationType': 'ApplicationRestricted',
'Permissions': (','.join([Permission.DELETE]))
}
"Content-Type": "application/x-www-form-urlencoded",
"AuthenticationType": "ApplicationRestricted",
"Permissions": (",".join([Permission.DELETE])),
},
}
pprint.pprint(delete_imms_handler(event, {}))
26 changes: 14 additions & 12 deletions backend/src/get_imms_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@
from fhir_controller import FhirController, make_controller
from models.errors import Severity, Code, create_operation_outcome
from log_structure import function_info
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE


@function_info
def get_imms_handler(event, context):
def get_imms_handler(event, context):
return get_immunization_by_id(event, make_controller())


def get_immunization_by_id(event, controller: FhirController):
try:
return controller.get_immunization_by_id(event)
except Exception as e:
exp_error = create_operation_outcome(resource_id=str(uuid.uuid4()), severity=Severity.error,
code=Code.server_error,
diagnostics=str(e))
except Exception: # pylint: disable = broad-exception-caught
exp_error = create_operation_outcome(
resource_id=str(uuid.uuid4()),
severity=Severity.error,
code=Code.server_error,
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
)
return FhirController.create_response(500, exp_error)


Expand All @@ -29,13 +33,11 @@ def get_immunization_by_id(event, controller: FhirController):
args = parser.parse_args()

event = {
"pathParameters": {
"id": args.id
},
"pathParameters": {"id": args.id},
"headers": {
'Content-Type': 'application/x-www-form-urlencoded',
'AuthenticationType': 'ApplicationRestricted',
'Permissions': (','.join([Permission.READ]))
}
"Content-Type": "application/x-www-form-urlencoded",
"AuthenticationType": "ApplicationRestricted",
"Permissions": (",".join([Permission.READ])),
},
}
pprint.pprint(get_imms_handler(event, {}))
46 changes: 24 additions & 22 deletions backend/src/search_imms_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from authorization import Permission
from fhir_controller import FhirController, make_controller
from models.errors import Severity, Code, create_operation_outcome
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE
from log_structure import function_info
import base64
import urllib.parse
Expand All @@ -21,26 +22,33 @@ def search_imms_handler(event: events.APIGatewayProxyEventV1, context: context_)

def search_imms(event: events.APIGatewayProxyEventV1, controller: FhirController):
try:
query_params = event.get('queryStringParameters', {})
body=event["body"]
query_params = event.get("queryStringParameters", {})
body = event["body"]
body_has_immunization_identifier = False
query_string_has_immunization_identifier = False
query_string_has_element =False
query_string_has_element = False
body_has_immunization_element = False
if not (query_params == None and body== None) :
if not (query_params == None and body == None):
if query_params:
query_string_has_immunization_identifier = 'immunization.identifier' in event.get('queryStringParameters', {})
query_string_has_element = '_element' in event.get('queryStringParameters', {})
query_string_has_immunization_identifier = "immunization.identifier" in event.get(
"queryStringParameters", {}
)
query_string_has_element = "_element" in event.get("queryStringParameters", {})
# Decode body from base64
if event['body']:
decoded_body = base64.b64decode(event['body']).decode('utf-8')
if event["body"]:
decoded_body = base64.b64decode(event["body"]).decode("utf-8")
# Parse the URL encoded body
parsed_body = urllib.parse.parse_qs(decoded_body)

# Check for 'immunization.identifier' in body
body_has_immunization_identifier = 'immunization.identifier' in parsed_body
body_has_immunization_element = '_element' in parsed_body
if query_string_has_immunization_identifier or body_has_immunization_identifier or query_string_has_element or body_has_immunization_element:
body_has_immunization_identifier = "immunization.identifier" in parsed_body
body_has_immunization_element = "_element" in parsed_body
if (
query_string_has_immunization_identifier
or body_has_immunization_identifier
or query_string_has_element
or body_has_immunization_element
):
return controller.get_immunization_by_identifier(event)
response = controller.search_immunizations(event)
else:
Expand All @@ -58,12 +66,12 @@ def search_imms(event: events.APIGatewayProxyEventV1, controller: FhirController
)
return FhirController.create_response(400, exp_error)
return response
except Exception as e:
except Exception: # pylint: disable = broad-exception-caught
exp_error = create_operation_outcome(
resource_id=str(uuid.uuid4()),
severity=Severity.error,
code=Code.server_error,
diagnostics=traceback.format_exc(),
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
)
return FhirController.create_response(500, exp_error)

Expand Down Expand Up @@ -92,15 +100,9 @@ def search_imms(event: events.APIGatewayProxyEventV1, controller: FhirController
help="Identifier of System",
type=str,
required=False,
dest="immunization_identifier"
)
parser.add_argument(
"--element",
help="Identifier of System",
type=str,
required=False,
dest="_element"
dest="immunization_identifier",
)
parser.add_argument("--element", help="Identifier of System", type=str, required=False, dest="_element")
args = parser.parse_args()

event: events.APIGatewayProxyEventV1 = {
Expand All @@ -111,7 +113,7 @@ def search_imms(event: events.APIGatewayProxyEventV1, controller: FhirController
"-date.to": [args.date_to] if args.date_to else [],
"_include": ["Immunization:patient"],
"immunization_identifier": [args.immunization_identifier] if args.immunization_identifier else [],
"_element": [args._element] if args._element else []
"_element": [args._element] if args._element else [],
},
"httpMethod": "POST",
"headers": {
Expand Down
Loading
Loading