From 085a803267a222913319d1e7eac0c5d41e578245 Mon Sep 17 00:00:00 2001 From: Jimmy Royer Date: Thu, 8 Sep 2022 09:02:20 -0400 Subject: [PATCH] Exploring how to make bearer token optional --- .vscode/launch.json | 6 +- app/celery/service_callback_tasks.py | 17 +++- app/models.py | 2 +- .../versions/0422_bearer_token_is_nullable.py | 20 ++++ .../app/celery/test_service_callback_tasks.py | 92 +++++++++++++------ 5 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 migrations/versions/0422_bearer_token_is_nullable.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 34617865e3..b6654cee67 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,8 @@ "type": "python", "request": "launch", "program": "${file}", - "console": "integratedTerminal" + "console": "integratedTerminal", + "justMyCode": false }, { "name": "Python: Flask", @@ -62,7 +63,8 @@ "--headless" ], "gevent": true, - "console": "integratedTerminal" + "console": "integratedTerminal", + "justMyCode": false, } ] } \ No newline at end of file diff --git a/app/celery/service_callback_tasks.py b/app/celery/service_callback_tasks.py index 19332880e0..37955a397e 100644 --- a/app/celery/service_callback_tasks.py +++ b/app/celery/service_callback_tasks.py @@ -57,15 +57,24 @@ def send_complaint_to_service(self, complaint_data): def _send_data_to_service_callback_api(self, data, service_callback_url, token, function_name): notification_id = data["notification_id"] if "notification_id" in data else data["id"] + + def _build_headers(token: str): + if token: + return { + "Content-Type": "application/json", + "Authorization": "Bearer {}".format(token), + } + else: + return { + "Content-Type": "application/json", + } + try: response = request( method="POST", url=service_callback_url, data=json.dumps(data), - headers={ - "Content-Type": "application/json", - "Authorization": "Bearer {}".format(token), - }, + headers=_build_headers(token), timeout=60, ) current_app.logger.info( diff --git a/app/models.py b/app/models.py index c5ae477142..16bbab411b 100644 --- a/app/models.py +++ b/app/models.py @@ -844,7 +844,7 @@ class ServiceCallbackApi(BaseModel, Versioned): service = db.relationship("Service", backref="service_callback_api") url = db.Column(db.String(), nullable=False) callback_type = db.Column(db.String(), db.ForeignKey("service_callback_type.name"), nullable=True) - _bearer_token = db.Column("bearer_token", db.String(), nullable=False) + _bearer_token = db.Column("bearer_token", db.String(), nullable=True) created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False) updated_at = db.Column(db.DateTime, nullable=True) updated_by = db.relationship("User") diff --git a/migrations/versions/0422_bearer_token_is_nullable.py b/migrations/versions/0422_bearer_token_is_nullable.py new file mode 100644 index 0000000000..c91643ba06 --- /dev/null +++ b/migrations/versions/0422_bearer_token_is_nullable.py @@ -0,0 +1,20 @@ +""" + +Revision ID: 0422_bearer_token_is_nullable +Revises: 0421_add_sms_daily_limit +Create Date: 2022-09-07 16:00:00 + +""" +import sqlalchemy as sa +from alembic import op + +revision = "0422_bearer_token_is_nullable" +down_revision = "0421_add_sms_daily_limit" + + +def upgrade(): + op.alter_column("service_callback_api", "bearer_token", nullable=True) + + +def downgrade(): + op.alter_column("service_callback_api", "bearer_token", nullable=False, server_default='') diff --git a/tests/app/celery/test_service_callback_tasks.py b/tests/app/celery/test_service_callback_tasks.py index e8bdd216ce..e46045fa08 100644 --- a/tests/app/celery/test_service_callback_tasks.py +++ b/tests/app/celery/test_service_callback_tasks.py @@ -60,33 +60,44 @@ def test_send_delivery_status_to_service_post_https_request_to_service_with_sign assert request_mock.request_history[0].headers["Authorization"] == "Bearer {}".format(callback_api.bearer_token) -def test_send_complaint_to_service_posts_https_request_to_service_with_signed_data( - notify_db_session, -): - with freeze_time("2001-01-01T12:00:00"): - callback_api, template = _set_up_test_data("email", "complaint") +@pytest.mark.parametrize("notification_type", ["email", "letter", "sms"]) +def test__send_data_to_service_callback_api_with_no_bearer_token(notify_db_session, mocker, notification_type): + callback_api, template = _set_up_test_data(notification_type, "delivery_status", None) - notification = create_notification(template=template) - complaint = create_complaint(service=template.service, notification=notification) - complaint_data = _set_up_data_for_complaint(callback_api, complaint, notification) - with requests_mock.Mocker() as request_mock: - request_mock.post(callback_api.url, json={}, status_code=200) - send_complaint_to_service(complaint_data) + datestr = datetime(2017, 6, 20) - mock_data = { - "notification_id": str(notification.id), - "complaint_id": str(complaint.id), - "reference": notification.client_reference, - "to": notification.to, - "complaint_date": datetime.utcnow().strftime(DATETIME_FORMAT), - } + notification = save_notification( + create_notification( + template=template, + created_at=datestr, + updated_at=datestr, + sent_at=datestr, + status="sent", + ) + ) + signed_status_update = _set_up_data_for_status_update(callback_api, notification) + with requests_mock.Mocker() as request_mock: + request_mock.post(callback_api.url, json={}, status_code=200) + send_delivery_status_to_service(notification.id, signed_status_update=signed_status_update) - assert request_mock.call_count == 1 - assert request_mock.request_history[0].url == callback_api.url - assert request_mock.request_history[0].method == "POST" - assert request_mock.request_history[0].text == json.dumps(mock_data) - assert request_mock.request_history[0].headers["Content-type"] == "application/json" - assert request_mock.request_history[0].headers["Authorization"] == "Bearer {}".format(callback_api.bearer_token) + mock_data = { + "id": str(notification.id), + "reference": notification.client_reference, + "to": notification.to, + "status": notification.status, + "provider_response": notification.provider_response, + "created_at": datestr.strftime(DATETIME_FORMAT), + "completed_at": datestr.strftime(DATETIME_FORMAT), + "sent_at": datestr.strftime(DATETIME_FORMAT), + "notification_type": notification_type, + } + + assert request_mock.call_count == 1 + assert request_mock.request_history[0].url == callback_api.url + assert request_mock.request_history[0].method == "POST" + assert request_mock.request_history[0].text == json.dumps(mock_data) + assert request_mock.request_history[0].headers["Content-type"] == "application/json" + assert request_mock.request_history[0].headers.get("Authorization") is None @pytest.mark.parametrize("notification_type", ["email", "letter", "sms"]) @@ -159,13 +170,42 @@ def test_send_delivery_status_to_service_succeeds_if_sent_at_is_none(notify_db_s assert mocked.call_count == 0 -def _set_up_test_data(notification_type, callback_type): +def test_send_complaint_to_service_posts_https_request_to_service_with_signed_data( + notify_db_session, +): + with freeze_time("2001-01-01T12:00:00"): + callback_api, template = _set_up_test_data("email", "complaint") + + notification = create_notification(template=template) + complaint = create_complaint(service=template.service, notification=notification) + complaint_data = _set_up_data_for_complaint(callback_api, complaint, notification) + with requests_mock.Mocker() as request_mock: + request_mock.post(callback_api.url, json={}, status_code=200) + send_complaint_to_service(complaint_data) + + mock_data = { + "notification_id": str(notification.id), + "complaint_id": str(complaint.id), + "reference": notification.client_reference, + "to": notification.to, + "complaint_date": datetime.utcnow().strftime(DATETIME_FORMAT), + } + + assert request_mock.call_count == 1 + assert request_mock.request_history[0].url == callback_api.url + assert request_mock.request_history[0].method == "POST" + assert request_mock.request_history[0].text == json.dumps(mock_data) + assert request_mock.request_history[0].headers["Content-type"] == "application/json" + assert request_mock.request_history[0].headers["Authorization"] == "Bearer {}".format(callback_api.bearer_token) + + +def _set_up_test_data(notification_type, callback_type, token="something_unique"): service = create_service(restricted=True) template = create_template(service=service, template_type=notification_type, subject="Hello") callback_api = create_service_callback_api( service=service, url="https://some.service.gov.uk/", - bearer_token="something_unique", + bearer_token=token, callback_type=callback_type, ) return callback_api, template