Skip to content

Commit 22f3a2f

Browse files
committed
AMB-1741 wire controller into get_imms_handler.py and update tests
1 parent fd3d546 commit 22f3a2f

File tree

6 files changed

+90
-142
lines changed

6 files changed

+90
-142
lines changed

lambda_code/src/fhir_controller.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import uuid
44

55
from fhir_service import FhirService
6-
from models.errors import ApiError, Severity, Code
6+
from models.errors import Severity, Code, create_operation_outcome
77

88

99
class FhirController:
@@ -16,22 +16,24 @@ def get_immunisation_by_id(self, aws_event) -> dict:
1616
imms_id = aws_event["pathParameters"]["id"]
1717

1818
if not re.match(self.immunisation_id_pattern, imms_id):
19-
api_error = ApiError(id=str(uuid.uuid4()), severity=Severity.error, code=Code.invalid,
20-
diagnostics="the provided event ID is either missing or not in the expected format.")
21-
res_body = api_error.to_uk_core().dict()
22-
return FhirController._create_response(400, json.dumps(res_body))
19+
msg = "the provided event ID is either missing or not in the expected format."
20+
api_error = create_operation_outcome(resource_id=str(uuid.uuid4()), severity=Severity.error,
21+
code=Code.invalid,
22+
diagnostics=msg)
23+
return FhirController.create_response(400, json.dumps(api_error.dict()))
2324

2425
resource = self.fhir_service.get_immunisation_by_id(imms_id)
2526
if resource:
26-
return FhirController._create_response(200, resource.json())
27+
return FhirController.create_response(200, resource.json())
2728
else:
28-
api_error = ApiError(id=str(uuid.uuid4()), severity=Severity.error, code=Code.not_found,
29-
diagnostics="The requested resource was not found.")
30-
res_body = api_error.to_uk_core().dict()
31-
return FhirController._create_response(404, json.dumps(res_body))
29+
msg = "The requested resource was not found."
30+
api_error = create_operation_outcome(resource_id=str(uuid.uuid4()), severity=Severity.error,
31+
code=Code.not_found,
32+
diagnostics=msg)
33+
return FhirController.create_response(404, json.dumps(api_error.dict()))
3234

3335
@staticmethod
34-
def _create_response(status_code, body):
36+
def create_response(status_code, body):
3537
return {
3638
"statusCode": status_code,
3739
"headers": {
Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,24 @@
1-
import json
2-
import re
31
import uuid
42

5-
from dynamodb import EventTable
63
from fhir_controller import FhirController
7-
8-
9-
def create_operation_outcome(event_id, message, code):
10-
return {
11-
"resourceType": "OperationOutcome",
12-
"id": event_id,
13-
"meta": {
14-
"profile": [
15-
"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"
16-
]
17-
},
18-
"issue": [
19-
{
20-
"severity": "error",
21-
"code": code,
22-
"details": {
23-
"coding": [
24-
{
25-
"system": "https://fhir.nhs.uk/Codesystem/http-error-codes",
26-
"code": code.upper()
27-
}
28-
]
29-
},
30-
"diagnostics": message
31-
}
32-
]
33-
}
4+
from fhir_repository import ImmunisationRepository
5+
from fhir_service import FhirService
6+
from models.errors import Severity, Code, create_operation_outcome
347

358

369
def get_imms_handler(event, context):
37-
dynamo_service = EventTable()
38-
return get_imms(event, dynamo_service)
39-
40-
41-
# create function which receives event and instance of dynamodb
42-
def get_imms(event, dynamo_service):
43-
event_id = event["pathParameters"]["id"]
44-
45-
def is_valid_id(_event_id):
46-
pattern = r'^[A-Za-z0-9\-.]{1,64}$'
47-
return re.match(pattern, _event_id) is not None
10+
imms_repo = ImmunisationRepository()
11+
service = FhirService(imms_repo=imms_repo)
12+
controller = FhirController(fhir_service=service)
4813

49-
if not is_valid_id(event_id) or not event_id:
50-
body = json.dumps(
51-
create_operation_outcome(str(uuid.uuid4()),
52-
"the provided event ID is either missing or not in the expected format.",
53-
"invalid"))
54-
return FhirController._create_response(400, body)
14+
return get_immunisation_by_id(event, controller)
5515

56-
query_result = dynamo_service.get_event_by_id(event_id)
57-
if query_result is None:
58-
body = json.dumps(
59-
create_operation_outcome(str(uuid.uuid4()), "The requested resource was not found.", "not-found"))
60-
return FhirController._create_response(404, body)
6116

62-
return FhirController._create_response(200, json.dumps(query_result))
17+
def get_immunisation_by_id(event, controller: FhirController):
18+
try:
19+
return controller.get_immunisation_by_id(event)
20+
except Exception as e:
21+
exp_error = create_operation_outcome(resource_id=str(uuid.uuid4()), severity=Severity.error,
22+
code=Code.not_found,
23+
diagnostics=str(e))
24+
return FhirController.create_response(500, exp_error.json())

lambda_code/src/models/errors.py

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from enum import Enum
22

33
from fhir.resources.operationoutcome import OperationOutcome
4-
from pydantic import BaseModel
54

65

76
class Severity(str, Enum):
@@ -13,35 +12,29 @@ class Code(str, Enum):
1312
invalid = "invalid"
1413

1514

16-
class ApiError(BaseModel, use_enum_values=True):
17-
id: str
18-
severity: Severity
19-
code: Code
20-
diagnostics: str
21-
22-
def to_uk_core(self) -> OperationOutcome:
23-
model = {
24-
"resourceType": "OperationOutcome",
25-
"id": self.id,
26-
"meta": {
27-
"profile": [
28-
"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"
29-
]
30-
},
31-
"issue": [
32-
{
33-
"severity": self.severity,
34-
"code": self.code,
35-
"details": {
36-
"coding": [
37-
{
38-
"system": "https://fhir.nhs.uk/Codesystem/http-error-codes",
39-
"code": self.code.upper()
40-
}
41-
]
42-
},
43-
"diagnostics": self.diagnostics
44-
}
15+
def create_operation_outcome(resource_id: str, severity: Severity, code: Code, diagnostics: str) -> OperationOutcome:
16+
model = {
17+
"resourceType": "OperationOutcome",
18+
"id": resource_id,
19+
"meta": {
20+
"profile": [
21+
"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"
4522
]
46-
}
47-
return OperationOutcome.parse_obj(model)
23+
},
24+
"issue": [
25+
{
26+
"severity": severity,
27+
"code": code,
28+
"details": {
29+
"coding": [
30+
{
31+
"system": "https://fhir.nhs.uk/Codesystem/http-error-codes",
32+
"code": code.upper()
33+
}
34+
]
35+
},
36+
"diagnostics": diagnostics
37+
}
38+
]
39+
}
40+
return OperationOutcome.parse_obj(model)

lambda_code/tests/test_api_errors.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@
44

55
sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}/../src")
66

7-
from models.errors import ApiError, Severity, Code
7+
from models.errors import Severity, Code, create_operation_outcome
88

99

1010
class TestApiErrors(unittest.TestCase):
11-
def test_error_to_uk_core(self):
11+
def test_error_to_uk_core2(self):
1212
code = Code.not_found
13+
1314
severity = Severity.error
1415
diag = "a-diagnostic"
1516
error_id = "a-id"
1617

17-
api_error = ApiError(id=error_id, severity=severity, code=code, diagnostics=diag)
18-
error = api_error.to_uk_core().dict()
18+
error = create_operation_outcome(resource_id=error_id, severity=severity, code=code, diagnostics=diag).dict()
1919

2020
issue = error["issue"][0]
2121
self.assertEqual(error["id"], error_id)
22-
self.assertEqual(issue["code"], "not_found")
22+
self.assertEqual(issue["code"], "not-found")
2323
self.assertEqual(issue["severity"], "error")
2424
self.assertEqual(issue["diagnostics"], diag)
25-
self.assertEqual(issue["details"]["coding"][0]["code"], "NOT_FOUND")
25+
self.assertEqual(issue["details"]["coding"][0]["code"], "NOT-FOUND")

lambda_code/tests/test_fhir_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def setUp(self):
1919

2020
def test_create_response(self):
2121
"""it should return application/fhir+json with correct status code"""
22-
res = self.controller._create_response(42, "a body")
22+
res = self.controller.create_response(42, "a body")
2323
headers = res["headers"]
2424

2525
self.assertEqual(res["statusCode"], 42)

lambda_code/tests/test_get_imms.py

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,51 @@
33
import sys
44
import unittest
55
import uuid
6-
from unittest.mock import MagicMock, create_autospec
6+
from unittest.mock import create_autospec
77

88
sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}/../src")
99

10-
from dynamodb import EventTable
11-
from get_imms_handler import get_imms, create_operation_outcome
10+
from get_imms_handler import get_immunisation_by_id
11+
from fhir_controller import FhirController
12+
from models.errors import Severity, Code, create_operation_outcome
1213

1314

1415
class TestGetImmunisationById(unittest.TestCase):
1516
def setUp(self):
16-
self.dynamodb_service = create_autospec(EventTable)
17+
self.controller = create_autospec(FhirController)
1718

18-
def test_get_imms_happy_path(self):
19-
# Arrange
20-
self.dynamodb_service.get_event_by_id = MagicMock(return_value={"message": "Mocked event data"})
21-
lambda_event = {"pathParameters": {"id": "sample-id"}}
19+
def test_get_immunisation_by_id(self):
20+
"""it should return immunisation by id"""
21+
lambda_event = {"pathParameters": {"id": "an-id"}}
22+
exp_res = {"a-key": "a-value"}
2223

23-
# Act
24-
result = get_imms(lambda_event, self.dynamodb_service)
24+
self.controller.get_immunisation_by_id.return_value = exp_res
2525

26-
# Assert
27-
self.dynamodb_service.get_event_by_id.assert_called_once_with(lambda_event["pathParameters"]["id"])
28-
self.assertEqual(result['statusCode'], 200)
29-
self.assertDictEqual(json.loads(result['body']), {"message": "Mocked event data"})
26+
# When
27+
act_res = get_immunisation_by_id(lambda_event, self.controller)
3028

31-
def test_get_imms_handler_sad_path_400(self):
32-
unformatted_event = {"pathParameters": {"id": "unexpected_id"}}
29+
# Then
30+
self.controller.get_immunisation_by_id.assert_called_once_with(lambda_event)
31+
self.assertDictEqual(exp_res, act_res)
3332

34-
# Act
35-
result = get_imms(unformatted_event, None)
33+
def test_handle_exception(self):
34+
"""unhandled exceptions should result in 500"""
35+
lambda_event = {"pathParameters": {"id": "an-id"}}
36+
error_msg = "an unhandled error"
37+
self.controller.get_immunisation_by_id.side_effect = Exception(error_msg)
3638

37-
# Assert
38-
assert result['statusCode'] == 400
39-
act_body = json.loads(result['body'])
40-
exp_body = create_operation_outcome(str(uuid.uuid4()),
41-
"he provided event ID is either missing or not in the expected format.",
42-
"invalid")
43-
act_body["id"] = None
44-
exp_body["id"] = None
45-
self.assertDictEqual(act_body, exp_body)
39+
exp_error = create_operation_outcome(resource_id=str(uuid.uuid4()), severity=Severity.error,
40+
code=Code.not_found,
41+
diagnostics=error_msg)
4642

47-
def test_get_imms_handler_sad_path_404(self):
48-
# Arrange
49-
self.dynamodb_service.get_event_by_id = MagicMock(return_value=None)
50-
incorrect_event = {"pathParameters": {"id": "incorrectid"}}
43+
# When
44+
act_res = get_immunisation_by_id(lambda_event, self.controller)
5145

52-
# Act
53-
result = get_imms(incorrect_event, self.dynamodb_service)
54-
55-
# Assert
56-
assert result['statusCode'] == 404
57-
self.assertEqual(result['headers']['Content-Type'], "application/fhir+json")
58-
act_body = json.loads(result['body'])
59-
exp_body = create_operation_outcome(str(uuid.uuid4()), "The requested resource was not found.", "not-found")
46+
# Then
47+
act_body = json.loads(act_res["body"])
6048
act_body["id"] = None
49+
exp_body = json.loads(exp_error.json()) # to and from json so, we get from OrderedDict to Dict
6150
exp_body["id"] = None
51+
6252
self.assertDictEqual(act_body, exp_body)
53+
self.assertEqual(act_res["statusCode"], 500)

0 commit comments

Comments
 (0)