Skip to content

Commit e11a336

Browse files
authored
Merge branch 'master' into dependabot/npm_and_yarn/npm-minor-patch-0d7b05022b
2 parents 4215f29 + d569421 commit e11a336

File tree

133 files changed

+954
-1647
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+954
-1647
lines changed

.github/dependabot.yml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ version: 2
77
updates:
88
- package-ecosystem: "docker"
99
directories:
10+
- "/batch_processor_filter"
11+
- "/infrastructure/grafana/non-prod/docker"
12+
- "/lambdas/filenameprocessor"
1013
- "/lambdas/ack_backend"
1114
- "/lambdas/delta_backend"
12-
- "/filenameprocessor"
13-
- "/infrastructure/grafana/non-prod/docker"
15+
- "/lambdas/recordprocessor"
1416
- "/mesh_processor"
15-
- "/recordprocessor"
1617
- "/sandbox"
1718
schedule:
1819
interval: "daily"
@@ -50,17 +51,17 @@ updates:
5051
- "/"
5152
- "/backend"
5253
- "/batch_processor_filter"
53-
- "/tests/e2e"
54-
- "/tests/e2e_batch"
55-
- "/filenameprocessor"
5654
- "/mesh_processor"
57-
- "/recordprocessor"
55+
- "/lambdas/recordprocessor"
5856
- "/lambdas/ack_backend"
5957
- "/lambdas/delta_backend"
60-
- "/lambdas/redis_sync"
58+
- "/lambdas/filenameprocessor"
6159
- "/lambdas/id_sync"
6260
- "/lambdas/mns_subscription"
61+
- "/lambdas/redis_sync"
6362
- "/lambdas/shared"
63+
- "/tests/e2e"
64+
- "/tests/e2e_batch"
6465
schedule:
6566
interval: "daily"
6667
open-pull-requests-limit: 1

.github/workflows/deploy-backend.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ jobs:
137137
if: ${{ inputs.environment == 'dev' && inputs.create_mns_subscription }}
138138
working-directory: "./lambdas/mns_subscription"
139139
env:
140+
APIGEE_ENVIRONMENT: int
140141
SQS_ARN: ${{ env.ID_SYNC_QUEUE_ARN }}
141142
run: |
142143
poetry install --no-root

.github/workflows/pr-teardown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ jobs:
6363
- name: Unsubscribe MNS
6464
working-directory: "./lambdas/mns_subscription"
6565
env:
66+
APIGEE_ENVIRONMENT: int
6667
SQS_ARN: ${{ env.ID_SYNC_QUEUE_ARN }}
6768
run: |
6869
poetry install --no-root

.github/workflows/quality-checks.yml

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,6 @@ jobs:
9797
aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
9898
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
9999
100-
- name: Run unittest with filenameprocessor-coverage
101-
working-directory: filenameprocessor
102-
id: filenameprocessor
103-
continue-on-error: true
104-
run: |
105-
poetry install
106-
poetry run coverage run -m unittest discover || echo "filenameprocessor tests failed" >> ../failed_tests.txt
107-
poetry run coverage xml -o ../filenameprocessor-coverage.xml
108-
109100
- name: Run unittest with batchprocessorfilter-coverage
110101
working-directory: batch_processor_filter
111102
id: batchprocessorfilter
@@ -118,15 +109,15 @@ jobs:
118109
poetry run coverage xml -o ../batchprocessorfilter-coverage.xml
119110
120111
- name: Run unittest with recordprocessor-coverage
121-
working-directory: recordprocessor
112+
working-directory: lambdas/recordprocessor
122113
id: recordprocessor
123114
env:
124-
PYTHONPATH: ${{ github.workspace }}/recordprocessor/src:${{ github.workspace }}/recordprocessor/tests
115+
PYTHONPATH: ${{ env.LAMBDA_PATH }}/recordprocessor/src:${{ env.LAMBDA_PATH }}/recordprocessor/tests:${{ env.SHARED_PATH }}/src
125116
continue-on-error: true
126117
run: |
127118
poetry install
128-
poetry run coverage run -m unittest discover || echo "recordprocessor tests failed" >> ../failed_tests.txt
129-
poetry run coverage xml -o ../recordprocessor-coverage.xml
119+
poetry run coverage run --source=src -m unittest discover || echo "recordprocessor tests failed" >> ../../failed_tests.txt
120+
poetry run coverage xml -o ../../recordprocessor-coverage.xml
130121
131122
# This step is redundant - all of these tests will be run in the backend step below
132123
- name: Run unittest with recordforwarder-coverage
@@ -182,6 +173,28 @@ jobs:
182173
poetry run coverage run -m unittest discover || echo "delta tests failed" >> ../../failed_tests.txt
183174
poetry run coverage xml -o ../../delta-coverage.xml
184175
176+
- name: Run unittest with filenameprocessor-coverage
177+
working-directory: lambdas/filenameprocessor
178+
id: filenameprocessor
179+
env:
180+
PYTHONPATH: ${{ env.LAMBDA_PATH }}/filenameprocessor/src:${{ env.LAMBDA_PATH }}/filenameprocessor/tests:${{ env.SHARED_PATH }}/src
181+
continue-on-error: true
182+
run: |
183+
poetry install
184+
poetry run coverage run -m unittest discover || echo "filenameprocessor tests failed" >> ../../failed_tests.txt
185+
poetry run coverage xml -o ../../filenameprocessor-coverage.xml
186+
187+
- name: Run unittest with id_sync
188+
working-directory: lambdas/id_sync
189+
id: id_sync
190+
env:
191+
PYTHONPATH: ${{ env.LAMBDA_PATH }}/id_sync/src:${{ env.LAMBDA_PATH }}/id_sync/tests:${{ env.SHARED_PATH }}/src
192+
continue-on-error: true
193+
run: |
194+
poetry install
195+
poetry run coverage run --rcfile=.coveragerc --source=src -m unittest discover || echo "id_sync tests failed" >> ../../failed_tests.txt
196+
poetry run coverage xml -o ../../id_sync-coverage.xml
197+
185198
- name: Run unittest with coverage-mns-subscription
186199
working-directory: lambdas/mns_subscription
187200
id: mns_subscription
@@ -216,17 +229,6 @@ jobs:
216229
poetry run coverage run --rcfile=.coveragerc --source=src -m unittest discover -s tests -p "test_*.py" -v || echo "shared tests failed" >> ../../failed_tests.txt
217230
poetry run coverage xml -o ../../shared-coverage.xml
218231
219-
- name: Run unittest with id_sync
220-
working-directory: lambdas/id_sync
221-
id: id_sync
222-
env:
223-
PYTHONPATH: ${{ env.LAMBDA_PATH }}/id_sync/src:${{ env.LAMBDA_PATH }}/id_sync/tests:${{ env.SHARED_PATH }}/src
224-
continue-on-error: true
225-
run: |
226-
poetry install
227-
poetry run coverage run --rcfile=.coveragerc --source=src -m unittest discover || echo "id_sync tests failed" >> ../../failed_tests.txt
228-
poetry run coverage xml -o ../../id_sync-coverage.xml
229-
230232
- name: Run Test Failure Summary
231233
id: check_failure
232234
run: |

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SHELL=/usr/bin/env bash -euo pipefail
22

3-
PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = backend batch_processor_filter filenameprocessor mesh_processor recordprocessor lambdas/ack_backend lambdas/delta_backend lambdas/redis_sync lambdas/id_sync lambdas/mns_subscription lambdas/shared
3+
PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = backend batch_processor_filter mesh_processor lambdas/ack_backend lambdas/delta_backend lambdas/filenameprocessor lambdas/id_sync lambdas/mns_subscription lambdas/recordprocessor lambdas/redis_sync lambdas/shared
44
PYTHON_PROJECT_DIRS = tests/e2e tests/e2e_batch quality_checks $(PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS)
55

66
.PHONY: install lint format format-check clean publish build-proxy release initialise-all-python-venvs update-all-python-dependencies run-all-python-unit-tests build-all-docker-images

backend/src/controller/fhir_api_exception_handler.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,27 @@
99
from controller.aws_apig_response_utils import create_response
1010
from models.errors import (
1111
Code,
12+
CustomValidationError,
13+
IdentifierDuplicationError,
1214
InvalidImmunizationId,
15+
InvalidJsonError,
1316
ResourceNotFoundError,
1417
Severity,
1518
UnauthorizedError,
1619
UnauthorizedVaxError,
20+
UnhandledResponseError,
1721
create_operation_outcome,
1822
)
1923

2024
_CUSTOM_EXCEPTION_TO_STATUS_MAP: dict[Type[Exception], int] = {
2125
InvalidImmunizationId: 400,
26+
InvalidJsonError: 400,
27+
CustomValidationError: 400,
2228
UnauthorizedError: 403,
2329
UnauthorizedVaxError: 403,
2430
ResourceNotFoundError: 404,
31+
IdentifierDuplicationError: 422,
32+
UnhandledResponseError: 500,
2533
}
2634

2735

backend/src/controller/fhir_controller.py

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import urllib.parse
66
import uuid
77
from decimal import Decimal
8+
from json import JSONDecodeError
89
from typing import Optional
910

1011
from aws_lambda_typing.events import APIGatewayProxyEventV1
@@ -20,11 +21,11 @@
2021
Code,
2122
IdentifierDuplicationError,
2223
InvalidImmunizationId,
24+
InvalidJsonError,
2325
ParameterException,
2426
Severity,
2527
UnauthorizedError,
2628
UnauthorizedVaxError,
27-
UnhandledResponseError,
2829
ValidationError,
2930
create_operation_outcome,
3031
)
@@ -48,7 +49,8 @@ def make_controller(
4849

4950

5051
class FhirController:
51-
immunization_id_pattern = r"^[A-Za-z0-9\-.]{1,64}$"
52+
_IMMUNIZATION_ID_PATTERN = r"^[A-Za-z0-9\-.]{1,64}$"
53+
_API_SERVICE_URL = get_service_url()
5254

5355
def __init__(
5456
self,
@@ -105,46 +107,22 @@ def get_immunization_by_id(self, aws_event: APIGatewayProxyEventV1) -> dict:
105107

106108
return create_response(200, resource.json(), {E_TAG_HEADER_NAME: version})
107109

108-
def create_immunization(self, aws_event):
109-
if not aws_event.get("headers"):
110-
return create_response(
111-
403,
112-
create_operation_outcome(
113-
resource_id=str(uuid.uuid4()),
114-
severity=Severity.error,
115-
code=Code.forbidden,
116-
diagnostics="Unauthorized request",
117-
),
118-
)
119-
120-
supplier_system = self._identify_supplier_system(aws_event)
110+
@fhir_api_exception_handler
111+
def create_immunization(self, aws_event: APIGatewayProxyEventV1) -> dict:
112+
supplier_system = get_supplier_system_header(aws_event)
121113

122114
try:
123-
immunisation = json.loads(aws_event["body"], parse_float=Decimal)
124-
except json.decoder.JSONDecodeError as e:
125-
return self._create_bad_request(f"Request's body contains malformed JSON: {e}")
126-
try:
127-
resource = self.fhir_service.create_immunization(immunisation, supplier_system)
128-
if "diagnostics" in resource:
129-
exp_error = create_operation_outcome(
130-
resource_id=str(uuid.uuid4()),
131-
severity=Severity.error,
132-
code=Code.invariant,
133-
diagnostics=resource["diagnostics"],
134-
)
135-
return create_response(400, json.dumps(exp_error))
136-
else:
137-
location = f"{get_service_url()}/Immunization/{resource.id}"
138-
version = "1"
139-
return create_response(201, None, {"Location": location, "E-Tag": version})
140-
except ValidationError as error:
141-
return create_response(400, error.to_operation_outcome())
142-
except IdentifierDuplicationError as duplicate:
143-
return create_response(422, duplicate.to_operation_outcome())
144-
except UnhandledResponseError as unhandled_error:
145-
return create_response(500, unhandled_error.to_operation_outcome())
146-
except UnauthorizedVaxError as unauthorized:
147-
return create_response(403, unauthorized.to_operation_outcome())
115+
immunisation: dict = json.loads(aws_event["body"], parse_float=Decimal)
116+
except JSONDecodeError as e:
117+
raise InvalidJsonError(message=str(f"Request's body contains malformed JSON: {e}"))
118+
119+
created_resource_id = self.fhir_service.create_immunization(immunisation, supplier_system)
120+
121+
return create_response(
122+
status_code=201,
123+
body=None,
124+
headers={"Location": f"{self._API_SERVICE_URL}/Immunization/{created_resource_id}", "E-Tag": "1"},
125+
)
148126

149127
def update_immunization(self, aws_event):
150128
try:
@@ -188,7 +166,7 @@ def update_immunization(self, aws_event):
188166
)
189167
return create_response(400, json.dumps(exp_error))
190168
# Validate the imms id in the path params and body of request - end
191-
except json.decoder.JSONDecodeError as e:
169+
except JSONDecodeError as e:
192170
return self._create_bad_request(f"Request's body contains malformed JSON: {e}")
193171
except Exception as e:
194172
return self._create_bad_request(f"Request's body contains string: {e}")
@@ -405,7 +383,7 @@ def search_immunizations(self, aws_event: APIGatewayProxyEventV1) -> dict:
405383

406384
def _is_valid_immunization_id(self, immunization_id: str) -> bool:
407385
"""Validates if the given unique Immunization ID is valid."""
408-
return False if not re.match(self.immunization_id_pattern, immunization_id) else True
386+
return False if not re.match(self._IMMUNIZATION_ID_PATTERN, immunization_id) else True
409387

410388
def _validate_identifier_system(self, _id: str, _elements: str) -> Optional[dict]:
411389
if not _id:

backend/src/create_imms_handler.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import argparse
22
import logging
33
import pprint
4-
import uuid
54

6-
from constants import GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE
7-
from controller.aws_apig_response_utils import create_response
85
from controller.fhir_controller import FhirController, make_controller
96
from local_lambda import load_string
107
from log_structure import function_info
11-
from models.errors import Code, Severity, create_operation_outcome
128

139
logging.basicConfig(level="INFO")
1410
logger = logging.getLogger()
@@ -20,17 +16,7 @@ def create_imms_handler(event, _context):
2016

2117

2218
def create_immunization(event, controller: FhirController):
23-
try:
24-
return controller.create_immunization(event)
25-
except Exception: # pylint: disable = broad-exception-caught
26-
logger.exception("Unhandled exception")
27-
exp_error = create_operation_outcome(
28-
resource_id=str(uuid.uuid4()),
29-
severity=Severity.error,
30-
code=Code.server_error,
31-
diagnostics=GENERIC_SERVER_ERROR_DIAGNOSTICS_MESSAGE,
32-
)
33-
return create_response(500, exp_error)
19+
return controller.create_immunization(event)
3420

3521

3622
if __name__ == "__main__":

backend/src/models/errors.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,21 @@ def to_operation_outcome(self) -> dict:
214214
)
215215

216216

217+
@dataclass
218+
class InvalidJsonError(RuntimeError):
219+
"""Raised when client provides an invalid JSON payload"""
220+
221+
message: str
222+
223+
def to_operation_outcome(self) -> dict:
224+
return create_operation_outcome(
225+
resource_id=str(uuid.uuid4()),
226+
severity=Severity.error,
227+
code=Code.invalid,
228+
diagnostics=self.message,
229+
)
230+
231+
217232
def create_operation_outcome(resource_id: str, severity: Severity, code: Code, diagnostics: str) -> dict:
218233
"""Create an OperationOutcome object. Do not use `fhir.resource` library since it adds unnecessary validations"""
219234
return {

0 commit comments

Comments
 (0)