Skip to content

Commit 7c01561

Browse files
authored
VED-79: Subscribe to MNS notification (#670)
VED-79: Subscribe to MNS notification
1 parent ae89126 commit 7c01561

26 files changed

+1932
-3
lines changed

.github/workflows/sonarcloud.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@ jobs:
103103
poetry run coverage run -m unittest discover || echo "mesh_processor tests failed" >> ../failed_tests.txt
104104
poetry run coverage xml -o ../mesh_processor-coverage.xml
105105
106+
- name: Run unittest with coverage-mns-subscription
107+
working-directory: mns_subscription
108+
env:
109+
PYTHONPATH: ${{ github.workspace }}/mns_subscription/src:${{ github.workspace }}/mns_subscription/tests
110+
id: mns_subscription
111+
continue-on-error: true
112+
run: |
113+
poetry install
114+
poetry run coverage run -m unittest discover || echo "mns_subscription tests failed" >> ../failed_tests.txt
115+
poetry run coverage report -m
116+
poetry run coverage xml -o ../mns_subscription-coverage.xml
117+
106118
- name: Run unittest with redis_sync
107119
working-directory: redis_sync
108120
id: redis_sync

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 = ack_backend backend delta_backend filenameprocessor mesh_processor recordprocessor redis_sync
3+
PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS = ack_backend backend delta_backend filenameprocessor mesh_processor recordprocessor redis_sync mns_subscription
44
PYTHON_PROJECT_DIRS = e2e e2e_batch $(PYTHON_PROJECT_DIRS_WITH_UNIT_TESTS)
55

66
#Installs dependencies using poetry.

azure/azure-pr-pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ extends:
4848
- template: ./templates/post-deploy.yml
4949
parameters:
5050
aws_account_type: 'dev'
51+
subscribe_to_mns: false

azure/azure-release-pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ extends:
4646
- template: ./templates/post-deploy.yml
4747
parameters:
4848
aws_account_type: 'dev'
49+
subscribe_to_mns: false
4950
- environment: sandbox
5051
proxy_path: sandbox
5152
jinja_templates:

azure/templates/post-deploy.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ parameters:
55
default: true
66
- name: 'aws_account_type'
77
type: string
8+
- name: subscribe_to_mns
9+
type: boolean
10+
default: true
811

912
steps:
1013
- ${{ if parameters.is_ptl }}:
@@ -66,16 +69,38 @@ steps:
6669
DYNAMODB_TABLE_NAME=$(make -s output name=dynamodb_table_name)
6770
AWS_SQS_QUEUE_NAME=$(make -s output name=aws_sqs_queue_name)
6871
AWS_SNS_TOPIC_NAME=$(make -s output name=aws_sns_topic_name)
72+
ID_SYNC_QUEUE_ARN=$(make -s output name=id_sync_queue_arn)
6973
echo "##vso[task.setvariable variable=DYNAMODB_TABLE_NAME]$DYNAMODB_TABLE_NAME"
7074
echo "##vso[task.setvariable variable=AWS_DOMAIN_NAME]$AWS_DOMAIN_NAME"
7175
echo "##vso[task.setvariable variable=IMMS_DELTA_TABLE_NAME]$IMMS_DELTA_TABLE_NAME"
7276
echo "##vso[task.setvariable variable=AWS_SQS_QUEUE_NAME]$AWS_SQS_QUEUE_NAME"
7377
echo "##vso[task.setvariable variable=AWS_SNS_TOPIC_NAME]$AWS_SNS_TOPIC_NAME"
78+
echo "##vso[task.setvariable variable=ID_SYNC_QUEUE_ARN]$ID_SYNC_QUEUE_ARN"
7479
fi
7580
displayName: Apply Terraform
7681
workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)"
7782
retryCountOnTaskFailure: 2
7883
84+
- ${{ if eq(parameters.subscribe_to_mns, true) }}:
85+
- bash: |
86+
export AWS_PROFILE=apim-dev
87+
echo "Subscribing SQS to MNS for notifications."
88+
pyenv install -s 3.11.11
89+
pyenv local 3.11.11
90+
echo "Setting up poetry environment..."
91+
poetry env use 3.11
92+
poetry install --no-root
93+
94+
echo "Setting PYTHONPATH..."
95+
export PYTHONPATH=$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)/mns_subscription
96+
97+
echo "Subscribing SQS to MNS for notifications..."
98+
poetry run python src/subscribe_mns.py
99+
displayName: "Run MNS Subscription"
100+
workingDirectory: "$(Pipeline.Workspace)/s/$(SERVICE_NAME)/$(SERVICE_ARTIFACT_NAME)/mns_subscription"
101+
env:
102+
SQS_ARN: "$(ID_SYNC_QUEUE_ARN)"
103+
79104
- bash: |
80105
set -ex
81106

immunisation-fhir-api.code-workspace

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
},
3030
{
3131
"path": "redis_sync"
32+
},
33+
{
34+
"path": "mns_subscription"
3235
}
3336
],
3437
"settings": {},

mns_subscription/Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
test:
2+
@PYTHONPATH=src:tests python -m unittest
3+
4+
coverage-run:
5+
@PYTHONPATH=src:tests coverage run -m unittest discover
6+
7+
coverage-report:
8+
coverage report -m
9+
10+
coverage-html:
11+
coverage html
12+
.PHONY: build package test

mns_subscription/models/errors.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import uuid
2+
from dataclasses import dataclass
3+
from enum import Enum
4+
5+
6+
class Severity(str, Enum):
7+
error = "error"
8+
warning = "warning"
9+
10+
11+
class Code(str, Enum):
12+
forbidden = "forbidden"
13+
not_found = "not-found"
14+
invalid = "invalid or missing access token"
15+
server_error = "internal server error"
16+
invariant = "invariant"
17+
incomplete = "parameter-incomplete"
18+
duplicate = "duplicate"
19+
# Added an unauthorized code its used when returning a response for an unauthorized vaccine type search.
20+
unauthorized = "unauthorized"
21+
22+
23+
@dataclass
24+
class UnauthorizedError(RuntimeError):
25+
response: dict | str
26+
message: str
27+
28+
def __str__(self):
29+
return f"{self.message}\n{self.response}"
30+
31+
@staticmethod
32+
def to_operation_outcome() -> dict:
33+
msg = "Unauthorized request"
34+
return create_operation_outcome(
35+
resource_id=str(uuid.uuid4()),
36+
severity=Severity.error,
37+
code=Code.forbidden,
38+
diagnostics=msg,
39+
)
40+
41+
42+
@dataclass
43+
class TokenValidationError(RuntimeError):
44+
response: dict | str
45+
message: str
46+
47+
def __str__(self):
48+
return f"{self.message}\n{self.response}"
49+
50+
@staticmethod
51+
def to_operation_outcome() -> dict:
52+
msg = "Missing/Invalid Token"
53+
return create_operation_outcome(
54+
resource_id=str(uuid.uuid4()),
55+
severity=Severity.error,
56+
code=Code.invalid,
57+
diagnostics=msg,
58+
)
59+
60+
61+
@dataclass
62+
class ConflictError(RuntimeError):
63+
response: dict | str
64+
message: str
65+
66+
def __str__(self):
67+
return f"{self.message}\n{self.response}"
68+
69+
@staticmethod
70+
def to_operation_outcome() -> dict:
71+
msg = "Conflict"
72+
return create_operation_outcome(
73+
resource_id=str(uuid.uuid4()),
74+
severity=Severity.error,
75+
code=Code.duplicate,
76+
diagnostics=msg,
77+
)
78+
79+
80+
@dataclass
81+
class ResourceNotFoundError(RuntimeError):
82+
"""Return this error when the requested resource does not exist or not complete"""
83+
84+
response: None
85+
message: str
86+
87+
def __str__(self):
88+
return f"{self.message}\n{self.response}"
89+
90+
def to_operation_outcome(self) -> dict:
91+
return create_operation_outcome(
92+
resource_id=str(uuid.uuid4()),
93+
severity=Severity.error,
94+
code=Code.not_found,
95+
diagnostics=self.__str__(),
96+
)
97+
98+
99+
@dataclass
100+
class UnhandledResponseError(RuntimeError):
101+
"""Use this unhandled errors"""
102+
103+
response: dict | str
104+
message: str
105+
106+
def __str__(self):
107+
return f"{self.message}\n{self.response}"
108+
109+
def to_operation_outcome(self) -> dict:
110+
return create_operation_outcome(
111+
resource_id=str(uuid.uuid4()),
112+
severity=Severity.error,
113+
code=Code.server_error,
114+
diagnostics=self.__str__(),
115+
)
116+
117+
118+
@dataclass
119+
class BadRequestError(RuntimeError):
120+
"""Use when payload is missing required parameters"""
121+
122+
response: dict | str
123+
message: str
124+
125+
def __str__(self):
126+
return f"{self.message}\n{self.response}"
127+
128+
def to_operation_outcome(self) -> dict:
129+
return create_operation_outcome(
130+
resource_id=str(uuid.uuid4()),
131+
severity=Severity.error,
132+
code=Code.incomplete,
133+
diagnostics=self.__str__(),
134+
)
135+
136+
137+
@dataclass
138+
class ServerError(RuntimeError):
139+
"""Use when there is a server error"""
140+
141+
response: dict | str
142+
message: str
143+
144+
def __str__(self):
145+
return f"{self.message}\n{self.response}"
146+
147+
def to_operation_outcome(self) -> dict:
148+
return create_operation_outcome(
149+
resource_id=str(uuid.uuid4()),
150+
severity=Severity.error,
151+
code=Code.server_error,
152+
diagnostics=self.__str__(),
153+
)
154+
155+
156+
def create_operation_outcome(resource_id: str, severity: Severity, code: Code, diagnostics: str) -> dict:
157+
"""Create an OperationOutcome object. Do not use `fhir.resource` library since it adds unnecessary validations"""
158+
return {
159+
"resourceType": "OperationOutcome",
160+
"id": resource_id,
161+
"meta": {"profile": ["https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"]},
162+
"issue": [
163+
{
164+
"severity": severity,
165+
"code": code,
166+
"details": {
167+
"coding": [
168+
{
169+
"system": "https://fhir.nhs.uk/Codesystem/http-error-codes",
170+
"code": code.upper(),
171+
}
172+
]
173+
},
174+
"diagnostics": diagnostics,
175+
}
176+
],
177+
}

0 commit comments

Comments
 (0)