Skip to content

Commit d5ba130

Browse files
committed
VED-79: modularize error, fix issues and increase coverage
1 parent dabcdec commit d5ba130

File tree

3 files changed

+78
-54
lines changed

3 files changed

+78
-54
lines changed

mns_subscription/models/errors.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,25 @@ def to_operation_outcome(self) -> dict:
115115
)
116116

117117

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.server_error,
133+
diagnostics=self.__str__(),
134+
)
135+
136+
118137
@dataclass
119138
class ServerError(RuntimeError):
120139
"""Use when there is a server error"""

mns_subscription/src/mns_service.py

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
ResourceFoundError,
1010
UnauthorizedError,
1111
ServerError,
12-
TokenValidationError
12+
BadRequestError,
13+
TokenValidationError,
14+
ConflictError
1315
)
1416

1517
SQS_ARN = os.getenv("SQS_ARN")
@@ -54,24 +56,8 @@ def subscribe_notification(self) -> dict | None:
5456

5557
if response.status_code in (200, 201):
5658
return response.json()
57-
elif response.status_code == 409:
58-
msg = "SQS Queue Already Subscribed, can't re-subscribe"
59-
raise UnhandledResponseError(response=response.json(), message=msg)
60-
elif response.status_code == 401:
61-
msg = "SQS Queue Already Subscribed, can't re-subscribe"
62-
raise TokenValidationError(response=response.json(), message=msg)
63-
elif response.status_code == 400:
64-
msg = "Resource Type provided for this is not correct"
65-
raise ResourceFoundError(response=response.json(), message=msg)
66-
elif response.status_code == 403:
67-
msg = "You don't have the right permissions for this request"
68-
raise UnauthorizedError(response=response.json(), message=msg)
69-
elif response.status_code == 500:
70-
msg = "Internal Server Error"
71-
raise ServerError(response=response.json(), message=msg)
7259
else:
73-
msg = f"Unhandled error: {response.status_code} - {response.text}"
74-
raise UnhandledResponseError(response=response.json(), message=msg)
60+
MnsService.handle_response(response)
7561

7662
def get_subscription(self) -> dict | None:
7763
response = requests.get(MNS_URL, headers=self.request_headers)
@@ -80,30 +66,16 @@ def get_subscription(self) -> dict | None:
8066

8167
if response.status_code == 200:
8268
bundle = response.json()
83-
# Assume a FHIR Bundle with 'entry' list
8469
for entry in bundle.get("entry", []):
8570
resource = entry.get("resource", entry)
8671
print(f"get resource sub: {resource}")
8772
logging.debug(f"get resource sub: {resource}")
8873
channel = resource.get("channel", {})
8974
if channel.get("endpoint") == SQS_ARN:
90-
return resource # Found a matching subscription
91-
return None # No subscription for this SQS ARN
92-
elif response.status_code == 401:
93-
msg = "Token validation failed for the request"
94-
raise TokenValidationError(response=response.json(), message=msg)
95-
elif response.status_code == 400:
96-
msg = "Bad request: Resource type or parameters incorrect"
97-
raise ResourceFoundError(response=response.json(), message=msg)
98-
elif response.status_code == 403:
99-
msg = "You don't have the right permissions for this request"
100-
raise UnauthorizedError(response=response.json(), message=msg)
101-
elif response.status_code == 500:
102-
msg = "Internal Server Error"
103-
raise ServerError(response=response.json(), message=msg)
75+
return resource
76+
return None
10477
else:
105-
msg = f"Unhandled error: {response.status_code} - {response.text}"
106-
raise UnhandledResponseError(response=response.json(), message=msg)
78+
MnsService.handle_response(response)
10779

10880
def check_subscription(self) -> dict:
10981
"""
@@ -130,20 +102,12 @@ def delete_subscription(self, subscription_id: str) -> bool:
130102
if response.status_code in (200, 204):
131103
logging.info(f"Deleted subscription {subscription_id}")
132104
return True
133-
elif response.status_code == 401:
134-
raise TokenValidationError(response=response.json(), message="Token validation failed for the request")
135-
elif response.status_code == 404:
136-
raise ResourceFoundError(response=response.json(), message=f"Subscription {subscription_id} not found")
137-
elif response.status_code == 403:
138-
raise UnauthorizedError(response=response.json(), message="No permission to delete subscription")
139-
elif response.status_code == 500:
140-
raise ServerError(response=response.json(), message="Internal Server Error")
141105
else:
142-
raise UnhandledResponseError(response=response.json(), message=f"Unhandled error: {response.status_code}")
106+
MnsService.handle_response(response)
143107

144108
def check_delete_subcription(self):
145109
try:
146-
resource = self.get_subscription() # Get the full resource dict
110+
resource = self.get_subscription()
147111
if not resource:
148112
return "No matching subscription found to delete."
149113

@@ -154,5 +118,20 @@ def check_delete_subcription(self):
154118
self.delete_subscription(subscription_id)
155119
return "Subscription successfully deleted"
156120
except Exception as e:
157-
# Optionally log the exception here
158121
return f"Error deleting subscription: {str(e)}"
122+
123+
@staticmethod
124+
def handle_response(response):
125+
error_mapping = {
126+
401: (TokenValidationError, "Token validation failed for the request"),
127+
400: (BadRequestError, "Bad request: Resource type or parameters incorrect"),
128+
403: (UnauthorizedError, "You don't have the right permissions for this request"),
129+
500: (ServerError, "Internal Server Error"),
130+
404: (ResourceFoundError, "Subscription or Resource not found"),
131+
409: (ConflictError, "SQS Queue Already Subscribed, can't re-subscribe")
132+
}
133+
exception_class, error_message = error_mapping.get(
134+
response.status_code,
135+
(UnhandledResponseError, f"Unhandled error: {response.status_code}"))
136+
137+
raise exception_class(response=response.json(), message=error_message)

mns_subscription/tests/test_mns_service.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
ServerError,
88
UnhandledResponseError,
99
TokenValidationError,
10+
BadRequestError,
1011
UnauthorizedError,
1112
ResourceFoundError)
1213

@@ -31,7 +32,7 @@ def test_successful_subscription(self, mock_get, mock_post):
3132
# Arrange GET to return no subscription found
3233
mock_get_response = MagicMock()
3334
mock_get_response.status_code = 200
34-
mock_get_response.json.return_value = {"entry": []} # No entries!
35+
mock_get_response.json.return_value = {"entry": []}
3536
mock_get.return_value = mock_get_response
3637

3738
# Arrange
@@ -59,10 +60,9 @@ def test_not_found_subscription(self, mock_post):
5960

6061
service = MnsService(self.authenticator)
6162

62-
with self.assertRaises(UnhandledResponseError) as context:
63+
with self.assertRaises(ResourceFoundError) as context:
6364
service.subscribe_notification()
64-
self.assertIn("404", str(context.exception))
65-
self.assertIn("Unhandled error", str(context.exception))
65+
self.assertIn("Subscription or Resource not found", str(context.exception))
6666

6767
@patch("mns_service.requests.post")
6868
def test_unhandled_error(self, mock_post):
@@ -105,7 +105,7 @@ def test_get_subscription_no_match(self, mock_get):
105105
"""Should return None when no subscription matches."""
106106
mock_response = MagicMock()
107107
mock_response.status_code = 200
108-
mock_response.json.return_value = {"entry": []} # No matches
108+
mock_response.json.return_value = {"entry": []}
109109
mock_get.return_value = mock_response
110110

111111
service = MnsService(self.authenticator)
@@ -124,8 +124,6 @@ def test_get_subscription_401(self, mock_get):
124124
with self.assertRaises(TokenValidationError):
125125
service.get_subscription()
126126

127-
# Similarly, you can add tests for 400, 403, 500, etc.
128-
129127
@patch("mns_service.requests.post")
130128
@patch("mns_service.requests.get")
131129
def test_check_subscription_creates_if_not_found(self, mock_get, mock_post):
@@ -150,7 +148,6 @@ def test_check_subscription_creates_if_not_found(self, mock_get, mock_post):
150148

151149
@patch("mns_service.requests.delete")
152150
def test_delete_subscription_success(self, mock_delete):
153-
# Test for both 200 and 204 (use a loop)
154151
for code in (200, 204):
155152
mock_response = MagicMock()
156153
mock_response.status_code = code
@@ -258,6 +255,35 @@ def test_check_delete_subscription_raises(self, mock_get_subscription, mock_dele
258255
result = service.check_delete_subcription()
259256
self.assertTrue(result.startswith("Error deleting subscription: Error!"))
260257

258+
def mock_response(self, status_code, json_data=None):
259+
mock_resp = MagicMock()
260+
mock_resp.status_code = status_code
261+
mock_resp.json.return_value = json_data or {"resource": "mock"}
262+
return mock_resp
263+
264+
def test_404_resource_found_error(self):
265+
resp = self.mock_response(404, {"resource": "Not found"})
266+
with self.assertRaises(ResourceFoundError) as context:
267+
MnsService.handle_response(resp)
268+
self.assertIn("Subscription or Resource not found", str(context.exception))
269+
self.assertEqual(context.exception.message, "Subscription or Resource not found")
270+
self.assertEqual(context.exception.response, {"resource": "Not found"})
271+
272+
def test_400_bad_request_error(self):
273+
resp = self.mock_response(400, {"resource": "Invalid"})
274+
with self.assertRaises(BadRequestError) as context:
275+
MnsService.handle_response(resp)
276+
self.assertIn("Bad request: Resource type or parameters incorrect", str(context.exception))
277+
self.assertEqual(context.exception.message, "Bad request: Resource type or parameters incorrect")
278+
self.assertEqual(context.exception.response, {"resource": "Invalid"})
279+
280+
def test_unhandled_status_code(self):
281+
resp = self.mock_response(418, {"resource": 1234})
282+
with self.assertRaises(UnhandledResponseError) as context:
283+
MnsService.handle_response(resp)
284+
self.assertIn("Unhandled error: 418", str(context.exception))
285+
self.assertEqual(context.exception.response, {"resource": 1234})
286+
261287

262288
if __name__ == "__main__":
263289
unittest.main()

0 commit comments

Comments
 (0)