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

#2172 - Retry Push for certain HTTP status codes #2266

Merged
merged 110 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
110 commits
Select commit Hold shift + click to select a range
f3019ce
First pass
k-macmillan Jan 23, 2025
e80ccc2
Merge branch 'main' into 2172-push-retries
k-macmillan Jan 23, 2025
a1ceb86
stuff was being import in init.py, causing circular imports
k-macmillan Jan 24, 2025
04fd501
Updated rest_push tests
k-macmillan Jan 24, 2025
abad0f0
Built tests for vetext responses
k-macmillan Jan 24, 2025
54efa68
Formatted
k-macmillan Jan 24, 2025
9c5a073
Built new tests
k-macmillan Jan 24, 2025
507e5c2
Merge branch 'main' into 2172-push-retries
k-macmillan Jan 24, 2025
d388b6a
Updated tests
k-macmillan Jan 28, 2025
21b8c24
Merge branch 'main' into 2172-push-retries
MackHalliday Jan 28, 2025
d73fe3d
Merge github.com:department-of-veterans-affairs/notification-api into…
MackHalliday Jan 28, 2025
9f8dc2e
#2171 - Update to resolve issues with test tests/app/v2/notifications…
MackHalliday Jan 29, 2025
8c6c9d6
Merge branch '2172-push-retries' of github.com:department-of-veterans…
MackHalliday Jan 29, 2025
1b9e1ee
#2172 - Add new internal
MackHalliday Jan 30, 2025
724dc33
#2172 - Update message for NonRetryablesException, missing field dict
MackHalliday Jan 30, 2025
70bddf5
#2172 - Fixing test in test_client for updates to vatext_client
MackHalliday Jan 30, 2025
badaaae
#2172 - Update mock for new celery task instead of vaclient
MackHalliday Jan 30, 2025
d21629e
Merge branch 'main' into 2172-push-retries
MackHalliday Jan 30, 2025
f64dfd7
#2172 - Test new internal endpoints
MackHalliday Jan 30, 2025
8b38fa2
#2172 - Clean up new internal endpoint, remove generic and correct sleep
MackHalliday Jan 30, 2025
cfec08d
#2172 - Removing assert statements from teststhat are no longer using…
MackHalliday Jan 30, 2025
a4cb75f
#2172 - Clean up testing for push_notifications
MackHalliday Jan 30, 2025
e17694e
#2172 - Remove TODO after talking to Kyle
MackHalliday Jan 30, 2025
6a9ba77
#2172 - Revert back changes on Exception after talking to Kyle
MackHalliday Jan 30, 2025
a22f5cf
#2172 - Clean up to test_push_broadcast
MackHalliday Jan 30, 2025
e05d4ce
#2172 - Fix test in test_client - test still failing'
MackHalliday Jan 30, 2025
b1f2562
#2172 - Correct keys in payload when sending to Vetext
MackHalliday Jan 30, 2025
260d21c
#2172 - Correct test that expected validation and formatting in the s…
MackHalliday Jan 30, 2025
4ac1ff8
#2172 - Fix tests for reformatting payload to vetext client
MackHalliday Jan 31, 2025
199b765
#2172 - Update testing for check auth fo new internal endpoint
MackHalliday Jan 31, 2025
805e040
#2172 - Clean up testing in the test_client, move tests into class
MackHalliday Jan 31, 2025
7b4852d
#2172 - Include missing docstring
MackHalliday Jan 31, 2025
d9a9c33
2172 - PR feedback Dave: Update parametrize. Do not need expected sta…
MackHalliday Feb 3, 2025
2bbba70
2172 - PR feedback Dave: Remove unnecessary general Exception in vali…
MackHalliday Feb 3, 2025
af738a8
2172 - Update logs to help with debugging
MackHalliday Feb 3, 2025
ad7ad30
2172 - Minor fix to logging
MackHalliday Feb 3, 2025
6aed1a4
2172 - Make adjustments to logging
MackHalliday Feb 3, 2025
8076deb
2172 - remove static method from format_for_vetext
MackHalliday Feb 3, 2025
a126db8
2172 - undo making format_for_vetext
MackHalliday Feb 3, 2025
a89ac4b
2172 - use apply async instead of delay
MackHalliday Feb 3, 2025
d2f2d22
2172 - Update async_apply to correctly pass in payload
MackHalliday Feb 3, 2025
c99f7df
2172 - Add mark where celery task failing
MackHalliday Feb 4, 2025
e2ae94b
#2172 - Fix logging in retry
MackHalliday Feb 4, 2025
2f4efe0
#2172 - Remove TODO
MackHalliday Feb 4, 2025
3690d3d
#2172 - Move format_for_vetext out of celery task and into API.
MackHalliday Feb 4, 2025
41f0297
Merge branch 'main' into 2172-push-retries
MackHalliday Feb 4, 2025
539e8f2
#2172 - Add extra log for request.text
MackHalliday Feb 4, 2025
a7dffc1
#2172 - Try reordering the keys in the vetext body
MackHalliday Feb 4, 2025
99a5d0f
#2172 - add payload to Vetext response log
MackHalliday Feb 4, 2025
556471b
#2172 - Add 2172 to logs
MackHalliday Feb 4, 2025
c5ce747
#2172 - Update format_for_vetext to return a v2pushload
MackHalliday Feb 4, 2025
0f1cf98
#2172 - Update format_for_vetext to return a v2pushload
MackHalliday Feb 4, 2025
c310886
Merge branch 'main' into 2172-push-retries
MackHalliday Feb 4, 2025
d90ede0
#2172 - Add logging for base url"
MackHalliday Feb 5, 2025
5392409
Merge branch '2172-push-retries' of github.com:department-of-veterans…
MackHalliday Feb 5, 2025
bb6a0a1
#2172 - Update payload to be dict in all logs
MackHalliday Feb 5, 2025
5643965
Merge branch 'main' into 2172-push-retries
MackHalliday Feb 5, 2025
27d888d
#2172 - Ruff clean up
MackHalliday Feb 5, 2025
10254ad
Merge branch '2172-push-retries' of github.com:department-of-veterans…
MackHalliday Feb 5, 2025
f561628
#2172 - Update to EMAIL Queue
MackHalliday Feb 5, 2025
f68e79f
#2172 - Revert payload passed into celery back to dict, correct testi…
MackHalliday Feb 5, 2025
70ad51f
#2172 - Add log to confirm payload is dict
MackHalliday Feb 5, 2025
6a9d081
#2172 - Add log to confirm payload is dict
MackHalliday Feb 5, 2025
685620d
#2172 - Add additional log
MackHalliday Feb 5, 2025
a945b05
#2172 - Remove log
MackHalliday Feb 5, 2025
82b1abe
#2172 - Add vetext user name and password
MackHalliday Feb 5, 2025
9c2a3da
#2172 - Update logging to remove number
MackHalliday Feb 6, 2025
5d66364
#2172 - Add vetext variables to perf
MackHalliday Feb 6, 2025
35afc98
#2172 - Add vetext variables to prod
MackHalliday Feb 6, 2025
c86e156
#2172 - Add vetext variables to staging
MackHalliday Feb 6, 2025
72c4eec
#2172 - Remove log for base url
MackHalliday Feb 6, 2025
0edfd08
#2172 - Remove password to trigger 500
MackHalliday Feb 6, 2025
60dfd17
#2172 - Remove password to trigger 500
MackHalliday Feb 6, 2025
7dc9253
#2172 - Add back password
MackHalliday Feb 6, 2025
363fbe6
#2172 - Update to aviod Missing environment sid for type
MackHalliday Feb 6, 2025
4b4e4e5
Revert "#2172 - Add back password"
MackHalliday Feb 6, 2025
0909ebc
#2172 - revert changes
MackHalliday Feb 6, 2025
f79e030
Merge branch 'main' into 2172-push-retries
MackHalliday Feb 6, 2025
88f7556
#2172 - PR feedback Cris - update to logger exception
MackHalliday Feb 6, 2025
6b7fd34
#2172 - PR feedback Cris - Update format to f string
MackHalliday Feb 6, 2025
152f5fd
#2172 - PR feedback Cris - Update exception for unable to reach celer…
MackHalliday Feb 6, 2025
33211c7
#2172 - PR feedback Cris - DEAFULT_MOBILE_APP_TYPE to DEFAULT_MOBILE_…
MackHalliday Feb 6, 2025
885d478
Merge branch '2172-push-retries' of github.com:department-of-veterans…
MackHalliday Feb 6, 2025
c2ad753
#2172 - PR feedback Dave - Try to mock call to vetext
MackHalliday Feb 7, 2025
4c8040e
#2172 - PR feedback Dave - Move log out of try/except
MackHalliday Feb 7, 2025
99df24a
#2172 - PR feedback Dave - Assert call vetext
MackHalliday Feb 7, 2025
eea2606
#2172 - PR feedback Dave - Assert call vetext, sad path
MackHalliday Feb 7, 2025
426bca0
#2172 - PR feedback Cris - refactor to format_for_vetext
MackHalliday Feb 7, 2025
98f2c88
#2172 - PR feedback Cris - Update logging to error
MackHalliday Feb 7, 2025
2913a02
#2172 - PR feedback Kyle - Redact icn from logs
MackHalliday Feb 7, 2025
85a7655
#2172 - Fix appSid value in testing
MackHalliday Feb 7, 2025
0c774ff
#2172 - Fix failing test due to celery mock, use request_history inst…
MackHalliday Feb 10, 2025
6dbd8f3
2172 - Update logs for vetext payload
MackHalliday Feb 10, 2025
6b02a97
2172 - Revert back run_test script
MackHalliday Feb 10, 2025
df80697
2172 - Throw a 500 error test
MackHalliday Feb 10, 2025
b6b1f50
2172 - REVERT Throw a 500 error test
MackHalliday Feb 10, 2025
0c73498
2172 - Throw a 500 error test
MackHalliday Feb 10, 2025
936c75b
Merge branch 'main' into 2172-push-retries
MackHalliday Feb 10, 2025
9f82c7a
2172 - Correct test to throw 500 error
MackHalliday Feb 10, 2025
8bc6eaf
Merge branch '2172-push-retries' of github.com:department-of-veterans…
MackHalliday Feb 10, 2025
285e790
2172 - REVERT Throw a 500 error test
MackHalliday Feb 10, 2025
2c6d960
Merge branch 'main' into 2172-push-retries
MackHalliday Feb 10, 2025
314698e
2172 - TODO #1487
MackHalliday Feb 10, 2025
dacadb5
Merge branch '2172-push-retries' of github.com:department-of-veterans…
MackHalliday Feb 10, 2025
c82550c
2172 - TODO #1487
MackHalliday Feb 10, 2025
5eace56
Update app/internal/rest.py
MackHalliday Feb 10, 2025
558f147
2172 - Try to copy payload before redact
MackHalliday Feb 10, 2025
500b82d
2172 - Try deepcopy instead
MackHalliday Feb 10, 2025
27223bb
2172 - throw 500 error
MackHalliday Feb 10, 2025
f5daa41
2172 - Revert throw 500 back
MackHalliday Feb 10, 2025
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
3 changes: 3 additions & 0 deletions app/celery/provider_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ def deliver_push(
retries = celery_task.request.retries

if retries < max_retries:
if 'icn' in payload:
payload['icn'] = '<redacted>'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you redact the ICN here, won't the retry attempt to use <redacted> instead of the icn? Or does this just affect the logs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


current_app.logger.warning(
'Push notification retrying: %s, max retries: %s, retries: %s', payload, max_retries, retries
)
Expand Down
1 change: 1 addition & 0 deletions app/internal/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
else:
response_body = {generic: request.json}

# TODO - will be addressed in TEAM-1487
MackHalliday marked this conversation as resolved.
Show resolved Hide resolved
return response_body, status_code

Check warning

Code scanning / CodeQL

Reflected server-side cross-site scripting Medium

Cross-site scripting vulnerability due to a
user-provided value
.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered in tests tests/app/internal/test_rest.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to resolve the CodeQL issue here. @k-macmillan maybe dismiss as false positive?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure - This internal endpoint does log the header from the incoming request.

@k-macmillan
Screenshot 2025-02-06 at 10 30 28 PM

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My gut tells me this is not great.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should never be logging the header since the headers all contain bearer tokens. But I don't think that is what CodeQL is complaining about.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cris-oddball talk to @k-macmillan about this - and we decided to make a ticket to address this issue since the code throwing the error was not code written for this PR.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put the ticket number in a comment near this, so the next time it flags we won't create a new ticket. AC of the new ticket should include removal of the comment. You can fill out the ticket details after requesting re-review of this PR.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


@internal_blueprint.route('/502', methods=['POST', 'GET'])
Expand Down
2 changes: 1 addition & 1 deletion app/mobile_app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .mobile_app_types import MobileAppType, DEAFULT_MOBILE_APP_TYPE # noqa
from .mobile_app_types import MobileAppType, DEFAULT_MOBILE_APP_TYPE # noqa
from .mobile_app import MobileApp # noqa
2 changes: 1 addition & 1 deletion app/mobile_app/mobile_app_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def get_app(
try:
return self._registry[app_type]
except KeyError:
self.logger.warning('Attempted to use an app that is not initialized: %s', app_type)
self.logger.exception('Attempted to use an app that is not initialized: %s', app_type)
raise

def get_registered_apps(self) -> List[MobileAppType]:
Expand Down
2 changes: 1 addition & 1 deletion app/mobile_app/mobile_app_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ def values() -> List['MobileAppType']:
return list(x.value for x in MobileAppType)


DEAFULT_MOBILE_APP_TYPE = MobileAppType.VA_FLAGSHIP_APP
DEFAULT_MOBILE_APP_TYPE = MobileAppType.VA_FLAGSHIP_APP
22 changes: 11 additions & 11 deletions app/v2/notifications/rest_push.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from app.celery.provider_tasks import deliver_push
from app.config import QueueNames
from app.constants import PUSH_TYPE
from app.mobile_app import DEAFULT_MOBILE_APP_TYPE, MobileAppType
from app.mobile_app import DEFAULT_MOBILE_APP_TYPE, MobileAppType
from app.schema_validation import validate
from app.utils import get_public_notify_type_text
from app.v2.errors import BadRequestError
Expand All @@ -34,31 +34,31 @@ def push_notification_helper(schema: dict):
the Flask "request" instance.
"""
if not authenticated_service.has_permissions(PUSH_TYPE):
public_notify_type = get_public_notify_type_text(PUSH_TYPE, plural=True)

raise BadRequestError(
message='Service is not allowed to send {}'.format(
get_public_notify_type_text(PUSH_TYPE, plural=True),
),
message=f'Service is not allowed to send {public_notify_type}',
status_code=403,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this isn't 400. If it should be 403, it seems like we should be raising a different exception.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is currently a 400. A 403 status code means access to a requested resource is forbidden. It occurs when a server understands a request but refuses to authorize it. In this case, that seems more appropriate (the service is not authorized to send push).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cris-oddball Thank you for responding to Dave

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I concur that 403 makes more sense. I'm suggesting that we should create a new exception, perhaps "PermissionDeniedError," rather than hack the BadRequestError. A bad request is 400.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh I gotcha. Yea, the BadRequestError threw me too. PermissionDeniedError seems better.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not use PermissionDeniedError anywhere in this code base - For 403 errors we generally use InvalidRequest, AuthError, or BadRequestError. I'm hesitant to add error type to this code base

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no issues with the BadRequestError. The class allows for any status code. The name of it could have been more narrow or InvalidRequest could have more derived classes but I don't think any of that is necessary for this ticket.

)

validated_payload = validate_push_payload(schema)

vetext_formatted_payload = vetext_client.format_for_vetext(validated_payload)

try:
current_app.logger.debug(
'Attempting to call deliver_push celery task with validated payload: %s',
vetext_formatted_payload,
)
current_app.logger.debug(
'Attempting to call deliver_push celery task with validated payload: %s',
vetext_formatted_payload,
)

try:
# Choosing to use the email queue for push to limit the number of empty queues
deliver_push.apply_async(
args=(vetext_formatted_payload,),
queue=QueueNames.SEND_EMAIL,
)
except (CeleryError, OperationalError):
current_app.logger.exception('Failed to enqueue deliver_push request')
response = jsonify(result='error', message='VA Notify service impaired, please try again'), 502
response = jsonify(result='error', message='VA Notify service impaired, please try again'), 503
else:
response = jsonify(result='success'), 201
# Flask turns the tuple into a json and status_code
Expand All @@ -84,7 +84,7 @@ def validate_push_payload(schema: dict[str, str]) -> V2PushPayload:
if 'mobile_app' in req_json:
app_sid = mobile_app_registry.get_app(MobileAppType[req_json['mobile_app']]).sid
else:
app_sid = mobile_app_registry.get_app(DEAFULT_MOBILE_APP_TYPE).sid
app_sid = mobile_app_registry.get_app(DEFAULT_MOBILE_APP_TYPE).sid
except (KeyError, TypeError) as e:
current_app.logger.warning('Push request failed validation due to mobile app setup: %s', e)
raise BadRequestError(message=str(e), status_code=400)
Expand Down
43 changes: 21 additions & 22 deletions app/va/vetext/client.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto Cris's question about changing the ICN value to "redacted". That's done twice in this file.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,20 @@ def init_app(

@staticmethod
def format_for_vetext(payload: V2PushPayload) -> dict[str, str]:
if payload.personalisation is not None:
if payload.personalisation:
payload.personalisation = {f'%{k.upper()}%': v for k, v in payload.personalisation.items()}

formatted_payload = {
'appSid': payload.app_sid,
'templateSid': payload.template_id,
'personalization': payload.personalisation,
}

if payload.is_broadcast():
formatted_payload = {
'appSid': payload.app_sid,
'templateSid': payload.template_id,
'topicSid': payload.topic_sid,
'personalization': payload.personalisation,
}
formatted_payload['topicSid'] = payload.topic_sid
else:
formatted_payload = {
'appSid': payload.app_sid,
'icn': payload.icn,
'templateSid': payload.template_id,
'personalization': payload.personalisation,
}
formatted_payload['icn'] = payload.icn

return formatted_payload

def send_push_notification(
Expand All @@ -68,12 +65,10 @@ def send_push_notification(
f'{self.base_url}/mobile/push/send', auth=self.auth, json=payload, timeout=self.TIMEOUT
)

self.logger.info(
'VEText response: %s for payload %s',
response.json() if response.ok else response.status_code,
payload,
self.logger.info('VEText response: %s', response.json() if response.ok else response.status_code)
self.logger.debug(
'VEText response: %s for payload %s', response.json() if response.ok else response.status_code, payload
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug log duplicates output from the preceding info log. Do we need to log the response json or status code twice in succession? Would including only the payload in the debug output be sufficient?

Copy link

@MackHalliday MackHalliday Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - This make is so much easy for the sake of testing - easy to link . We need the info log for upper environments .

self.logger.info('VEText response text %s', response.text)
response.raise_for_status()
except requests.exceptions.ReadTimeout:
# Discussion with VEText: read timeouts are still processed, so no need to retry
Expand All @@ -91,15 +86,19 @@ def send_push_notification(
elif e.response.status_code == 400:
self._decode_bad_request_response(e)
else:
payload['icn'] = '<redacted>'
if 'icn' in payload:
payload['icn'] = '<redacted>'

self.logger.exception(
'Status: %s - Not retrying - payload: %s',
e.response.status_code,
payload,
)
raise NonRetryableException from e
except requests.RequestException as e:
payload['icn'] = '<redacted>'
if 'icn' in payload:
payload['icn'] = '<redacted>'

self.logger.exception(
'Exception raised sending push notification. Not retrying - payload: %s',
payload,
Expand Down Expand Up @@ -128,9 +127,9 @@ def _decode_bad_request_response(
payload = http_exception.response.json()
field = payload.get('idType')
message = payload.get('error')
self.logger.warning('Bad response from VEText: %s with field: ', message, field)
self.logger.error('Bad response from VEText: %s with field: ', message, field)
raise NonRetryableException from http_exception
except Exception:
message = http_exception.response.text
self.logger.warning('Bad response from VEText: %s', message)
self.logger.error('Bad response from VEText: %s', message)
raise NonRetryableException from http_exception
51 changes: 42 additions & 9 deletions tests/app/celery/test_provider_tasks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from requests import HTTPError, Response
from requests.exceptions import ConnectTimeout, RequestException
from app.mobile_app.mobile_app_types import MobileAppType
import pytest

from app.celery.exceptions import AutoRetryException, NonRetryableException
Expand All @@ -19,7 +20,6 @@
NotificationTechnicalFailureException,
InvalidProviderException,
)
from app.mobile_app import DEAFULT_MOBILE_APP_TYPE
from app.models import Notification
from app.v2.errors import RateLimitError
from collections import namedtuple
Expand Down Expand Up @@ -501,14 +501,17 @@ def test_deliver_push_happy_path_icn(
client,
rmock,
):
url = f'{client.application.config["VETEXT_URL"]}/mobile/push/send'

rmock.register_uri(
'POST',
f'{client.application.config["VETEXT_URL"]}/mobile/push/send',
url,
json={'message': 'success'},
status_code=201,
)

formatted_payload = {
'appSid': DEAFULT_MOBILE_APP_TYPE,
'appSid': f'{MobileAppType.VA_FLAGSHIP_APP}_SID',
'templateSid': '2222',
'icn': '3333',
'personalization': {'%MSG_ID': '4444'},
Expand All @@ -517,19 +520,27 @@ def test_deliver_push_happy_path_icn(
# Should run without exceptions
deliver_push(formatted_payload)
kalbfled marked this conversation as resolved.
Show resolved Hide resolved

assert rmock.called
assert rmock.request_history[0].method == 'POST'
assert rmock.request_history[0].url == url
assert rmock.request_history[0].json()


def test_deliver_push_happy_path_topic(
client,
rmock,
):
url = f'{client.application.config["VETEXT_URL"]}/mobile/push/send'

rmock.register_uri(
'POST',
f'{client.application.config["VETEXT_URL"]}/mobile/push/send',
url,
json={'message': 'success'},
status_code=201,
)

formatted_payload = {
'appSid': DEAFULT_MOBILE_APP_TYPE,
'appSid': f'{MobileAppType.VA_FLAGSHIP_APP}_SID',
'templateSid': '2222',
'topicSid': '3333',
'personalization': {'%MSG_ID': '4444'},
Expand All @@ -538,6 +549,11 @@ def test_deliver_push_happy_path_topic(
# Should run without exceptions
deliver_push(formatted_payload)
kalbfled marked this conversation as resolved.
Show resolved Hide resolved

assert rmock.called
assert rmock.request_history[0].method == 'POST'
assert rmock.request_history[0].url == url
assert rmock.request_history[0].json()


@pytest.mark.parametrize(
'test_exception, status_code',
Expand All @@ -558,13 +574,16 @@ def test_deliver_push_retryable_exception(
):
if status_code is not None:
test_exception.response.status_code = status_code

url = f'{client.application.config["VETEXT_URL"]}/mobile/push/send'

rmock.register_uri(
'POST',
f'{client.application.config["VETEXT_URL"]}/mobile/push/send',
url,
exc=test_exception,
)
formatted_payload = {
'appSid': DEAFULT_MOBILE_APP_TYPE,
'appSid': f'{MobileAppType.VA_FLAGSHIP_APP}_SID',
'templateSid': '2222',
'icn': '3333',
'personalization': {'%MSG_ID': '4444'},
Expand All @@ -573,6 +592,11 @@ def test_deliver_push_retryable_exception(
with pytest.raises(AutoRetryException):
deliver_push(formatted_payload)

assert rmock.called
assert rmock.request_history[0].method == 'POST'
assert rmock.request_history[0].url == url
assert rmock.request_history[0].json()


@pytest.mark.parametrize(
'test_exception, status_code',
Expand All @@ -591,17 +615,26 @@ def test_deliver_push_nonretryable_exception(
):
if status_code is not None:
test_exception.response.status_code = status_code

url = f'{client.application.config["VETEXT_URL"]}/mobile/push/send'

rmock.register_uri(
'POST',
f'{client.application.config["VETEXT_URL"]}/mobile/push/send',
url,
exc=test_exception,
)

formatted_payload = {
'appSid': DEAFULT_MOBILE_APP_TYPE,
'appSid': f'{MobileAppType.VA_FLAGSHIP_APP}_SID',
'templateSid': '2222',
'icn': '3333',
'personalization': {'%MSG_ID': '4444'},
}

with pytest.raises(NonRetryableException):
deliver_push(formatted_payload)

assert rmock.called
assert rmock.request_history[0].method == 'POST'
assert rmock.request_history[0].url == url
assert rmock.request_history[0].json()
2 changes: 1 addition & 1 deletion tests/app/integration/test_push_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,4 @@ def test_mobile_app_push_notification_celery_exception(
)

assert response.json.get('result') == 'error'
assert response.status_code == 502
assert response.status_code == 503
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,4 @@ def test_celery_returns_502(self, client, sample_api_key, sample_service, mocker
mocker.patch('app.v2.notifications.rest_push.deliver_push.apply_async', side_effect=side_effect)
service = sample_service(service_permissions=[PUSH_TYPE])
response = post_send_push_broadcast_notification(client, sample_api_key(service), PUSH_BROADCAST_REQUEST)
assert response.status_code == 502
assert response.status_code == 503
2 changes: 1 addition & 1 deletion tests/app/v2/notifications/test_push_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,4 @@ def test_celery_returns_502(self, client, sample_api_key, sample_service, mocker
mocker.patch('app.v2.notifications.rest_push.deliver_push.apply_async', side_effect=side_effect)
service = sample_service(service_permissions=[PUSH_TYPE])
response = post_send_notification(client, sample_api_key(service), PUSH_TYPE, PUSH_REQUEST)
assert response.status_code == 502
assert response.status_code == 503
16 changes: 8 additions & 8 deletions tests/app/va/vetext/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from requests_mock import ANY

from app.celery.exceptions import NonRetryableException, RetryableException
from app.mobile_app import DEAFULT_MOBILE_APP_TYPE
from app.mobile_app import DEFAULT_MOBILE_APP_TYPE
from app.va.identifier import IdentifierType
from app.va.vetext import VETextClient
from app.v2.dataclasses import V2PushPayload
Expand Down Expand Up @@ -162,36 +162,36 @@ def test_raises_bad_request_exception_when_400_error_not_json(self, rmock, test_
class TestFormatVetextPayload:
cris-oddball marked this conversation as resolved.
Show resolved Hide resolved
def test_format_happy_path_push(self):
payload = V2PushPayload(
DEAFULT_MOBILE_APP_TYPE,
DEFAULT_MOBILE_APP_TYPE,
'any_template_id',
personalisation={'foo_1': 'bar', 'baz_two': 'abc', 'tmp': '123'},
icn='some_icn',
)
formatted_payload = VETextClient.format_for_vetext(payload)
expected_formatted_payload = {
'appSid': DEAFULT_MOBILE_APP_TYPE,
'appSid': DEFAULT_MOBILE_APP_TYPE,
'icn': 'some_icn',
'personalization': {'%FOO_1%': 'bar', '%BAZ_TWO%': 'abc', '%TMP%': '123'},
'templateSid': 'any_template_id',
}
assert formatted_payload == expected_formatted_payload

def test_format_happy_path_broadcast(self):
payload = V2PushPayload(DEAFULT_MOBILE_APP_TYPE, 'any_template_id', topic_sid='some_topic_sid')
payload = V2PushPayload(DEFAULT_MOBILE_APP_TYPE, 'any_template_id', topic_sid='some_topic_sid')
formatted_payload = VETextClient.format_for_vetext(payload)
expected_formatted_payload = {
'appSid': DEAFULT_MOBILE_APP_TYPE,
'appSid': DEFAULT_MOBILE_APP_TYPE,
'personalization': None,
'templateSid': 'any_template_id',
'topicSid': 'some_topic_sid',
}
assert formatted_payload == expected_formatted_payload

def test_format_personalisation_happy_path(self):
payload = V2PushPayload(DEAFULT_MOBILE_APP_TYPE, 'any_template_id')
payload = V2PushPayload(DEFAULT_MOBILE_APP_TYPE, 'any_template_id')
formatted_payload = VETextClient.format_for_vetext(payload)
expected_formatted_payload = {
'appSid': DEAFULT_MOBILE_APP_TYPE,
'appSid': DEFAULT_MOBILE_APP_TYPE,
'icn': None,
'personalization': None,
'templateSid': 'any_template_id',
Expand All @@ -205,7 +205,7 @@ def sample_vetext_push_payload(self, test_vetext_client):
"""Defaults to ICN (normal push, not broadcast)"""

def _wrapper(
mobile_app: str = DEAFULT_MOBILE_APP_TYPE,
mobile_app: str = DEFAULT_MOBILE_APP_TYPE,
template_id: str = 'any_template_id',
icn: str | None = 'some_icn',
topic_sid: str | None = None,
Expand Down