diff --git a/queue_services/account-mailer/logging.conf b/queue_services/account-mailer/logging.conf index ffc1a01e36..ded5cb81c1 100644 --- a/queue_services/account-mailer/logging.conf +++ b/queue_services/account-mailer/logging.conf @@ -1,5 +1,5 @@ [loggers] -keys=root,api +keys=root,api,asyncio [handlers] keys=console @@ -11,6 +11,12 @@ keys=simple level=DEBUG handlers=console +[logger_asyncio] +level=DEBUG +handlers=console +qualname=asyncio +propagate=0 + [logger_api] level=DEBUG handlers=console @@ -25,4 +31,4 @@ args=(sys.stdout,) [formatter_simple] format=%(asctime)s - %(name)s - %(levelname)s in %(module)s:%(filename)s:%(lineno)d - %(funcName)s: %(message)s -datefmt= \ No newline at end of file +datefmt= diff --git a/queue_services/account-mailer/src/account_mailer/email_processors/account_unlock.py b/queue_services/account-mailer/src/account_mailer/email_processors/account_unlock.py new file mode 100644 index 0000000000..de878e3e9d --- /dev/null +++ b/queue_services/account-mailer/src/account_mailer/email_processors/account_unlock.py @@ -0,0 +1,94 @@ +# Copyright © 2019 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""A Template for the Account Unlocked Email.""" + +import base64 +import datetime + +from auth_api.services.rest_service import RestService +from auth_api.utils.enums import AuthHeaderType, ContentType +from entity_queue_common.service_utils import logger +from flask import current_app +from jinja2 import Template + +from account_mailer.email_processors import generate_template + + +def process(email_msg: dict, token: str) -> dict: + """Build the email for Account Unlocked notification.""" + logger.debug('email_msg notification: %s', email_msg) + pdf_attachment = _get_account_unlock_pdf(email_msg, token) + html_body = _get_account_unlock_email(email_msg) + return { + 'recipients': email_msg.get('admin_coordinator_emails'), + 'content': { + 'subject': email_msg.get('subject'), + 'body': f'{html_body}', + 'attachments': [ + { + 'fileName': 'Account_Unlock_Receipt.pdf', + 'fileBytes': pdf_attachment.decode('utf-8'), + 'fileUrl': '', + 'attachOrder': '1' + } + ] + } + } + + +def _get_account_unlock_email(email_msg): + filled_template = generate_template(current_app.config.get('TEMPLATE_PATH'), email_msg.get('template_name')) + jnja_template = Template(filled_template, autoescape=True) + html_out = jnja_template.render( + account_name=email_msg.get('account_name'), + logo_url=email_msg.get('logo_url') + ) + return html_out + + +def _get_account_unlock_pdf(email_msg, token): + current_time = datetime.datetime.now() + template_vars = { + **email_msg, + 'corpName': email_msg.get('account_name'), + 'receiptNumber': email_msg.get('receipt_number'), + 'filingDate': current_time.strftime('%Y-%m-%d'), + 'effectiveDateTime': current_time.strftime('%Y-%m-%d %H:%M:%S'), + 'filingIdentifier': email_msg.get('filing_identifier'), + 'paymentMethodDescription': email_msg.get('payment_method_description'), + 'invoiceNumber': email_msg.get('invoice_number'), + 'invoice': email_msg.get('invoice') + } + + pdf_payload = { + 'reportName': 'NSF_Fee_Receipt', + 'templateVars': template_vars, + 'populatePageNumber': True, + 'templateName': 'payment_receipt', + } + + report_response = RestService.post(endpoint=current_app.config.get('REPORT_API_BASE_URL'), + token=token, + auth_header_type=AuthHeaderType.BEARER, + content_type=ContentType.JSON, + data=pdf_payload, + raise_for_status=True, + additional_headers={'Accept': 'application/pdf'}) + pdf_attachment = None + if report_response.status_code != 200: + logger.error('Failed to get pdf') + else: + pdf_attachment = base64.b64encode(report_response.content) + + return pdf_attachment diff --git a/queue_services/account-mailer/src/account_mailer/email_templates/account_restored_email.html b/queue_services/account-mailer/src/account_mailer/email_templates/account_restored_email.html deleted file mode 100644 index 15e2f082f1..0000000000 --- a/queue_services/account-mailer/src/account_mailer/email_templates/account_restored_email.html +++ /dev/null @@ -1,9 +0,0 @@ -# Your BC Registries and Online Services account has been reactivated. - -The following account has been reactivated and is now accessible. - -{{ account_name }} - -Log in to BC Registries and Online Services to access your account. - -[Log in]({{ url }}) diff --git a/queue_services/account-mailer/src/account_mailer/email_templates/account_unlocked_email.html b/queue_services/account-mailer/src/account_mailer/email_templates/account_unlocked_email.html new file mode 100644 index 0000000000..094e0132d4 --- /dev/null +++ b/queue_services/account-mailer/src/account_mailer/email_templates/account_unlocked_email.html @@ -0,0 +1,11 @@ +# Account Unlocked + +Your account **{{ account_name }}** has been unlocked. Team members can now log in and access this account. + +Your receipt is attached to this email: + +**Business Registry** +BC Registries and Online Services +Toll Free: [1-877-370-1033](1-877-370-1033) +Victoria Office: [250-370-1033](250-370-1033) +Email: [BCRegistries@gov.bc.ca](BCRegistries@gov.bc.ca) diff --git a/queue_services/account-mailer/src/account_mailer/enums.py b/queue_services/account-mailer/src/account_mailer/enums.py index bd5aed7d0e..e731a8c774 100644 --- a/queue_services/account-mailer/src/account_mailer/enums.py +++ b/queue_services/account-mailer/src/account_mailer/enums.py @@ -70,7 +70,7 @@ class SubjectType(Enum): """Event Types.""" NSF_LOCK_ACCOUNT_SUBJECT = '[BC Registries and Online Services] Your account has been suspended' - NSF_UNLOCK_ACCOUNT_SUBJECT = '[BC Registries and Online Services] Your account has been reactivated' + NSF_UNLOCK_ACCOUNT_SUBJECT = 'Your Account Was Successfully Restored' ACCOUNT_CONF_OVER_SUBJECT = '[BC Registries and Online Services] Your account is now active' PAD_INVOICE_CREATED = '[BC Registries and Online Services] Your accounts PAD transaction details' ADMIN_REMOVED_SUBJECT = '[BC Registries and Online Services] You have been removed as an administrator' @@ -160,7 +160,7 @@ class TemplateType(Enum): """Template Types.""" NSF_LOCK_ACCOUNT_TEMPLATE_NAME = 'account_suspended_email' - NSF_UNLOCK_ACCOUNT_TEMPLATE_NAME = 'account_restored_email' + NSF_UNLOCK_ACCOUNT_TEMPLATE_NAME = 'account_unlocked_email' ACCOUNT_CONF_OVER_TEMPLATE_NAME = 'account_conf_over_email' PAD_INVOICE_CREATED_TEMPLATE_NAME = 'pad_invoice_email' ADMIN_REMOVED_TEMPLATE_NAME = 'admin_removed_email' diff --git a/queue_services/account-mailer/src/account_mailer/worker.py b/queue_services/account-mailer/src/account_mailer/worker.py index 0d2acc5a80..b198e47f63 100644 --- a/queue_services/account-mailer/src/account_mailer/worker.py +++ b/queue_services/account-mailer/src/account_mailer/worker.py @@ -41,7 +41,8 @@ from account_mailer import config from account_mailer.auth_utils import get_login_url, get_member_emails from account_mailer.email_processors import ( - common_mailer, ejv_failures, pad_confirmation, payment_completed, product_confirmation, refund_requested) + account_unlock, common_mailer, ejv_failures, pad_confirmation, payment_completed, product_confirmation, + refund_requested) from account_mailer.enums import Constants, MessageType, SubjectType, TemplateType, TitleType from account_mailer.services import minio_service, notification_service from account_mailer.utils import format_currency, format_day_with_suffix, get_local_formatted_date @@ -93,14 +94,25 @@ async def process_event(event_message: dict, flask_app): email_dict = common_mailer.process(org_id, admin_coordinator_emails, template_name, subject, logo_url=logo_url) elif message_type == MessageType.NSF_UNLOCK_ACCOUNT.value: - logger.debug('unlock account message recieved') + logger.debug('unlock account message received') template_name = TemplateType.NSF_UNLOCK_ACCOUNT_TEMPLATE_NAME.value org_id = email_msg.get('accountId') admin_coordinator_emails = get_member_emails(org_id, (ADMIN, COORDINATOR)) subject = SubjectType.NSF_UNLOCK_ACCOUNT_SUBJECT.value logo_url = email_msg.get('logo_url') - email_dict = common_mailer.process(org_id, admin_coordinator_emails, - template_name, subject, logo_url=logo_url) + + email_dict = { + 'account_name': email_msg.get('toOrgName'), + 'logo_url': logo_url, + 'template_name': template_name, + 'subject': subject, + 'org_id': org_id, + 'admin_coordinator_emails': admin_coordinator_emails, + 'filing_identifier': email_msg.get('filing_identifier'), + } + + email_dict = account_unlock.process(email_msg=email_dict, token=token) + elif message_type == MessageType.ACCOUNT_CONFIRMATION_PERIOD_OVER.value: template_name = TemplateType.ACCOUNT_CONF_OVER_TEMPLATE_NAME.value org_id = email_msg.get('accountId') @@ -354,6 +366,7 @@ async def cb_subscription_handler(msg: nats.aio.client.Msg): event_message = json.loads(msg.data.decode('utf-8')) logger.debug('Event Message Received: %s', event_message) await process_event(event_message, FLASK_APP) - except Exception: # NOQA # pylint: disable=broad-except + except Exception as e: # NOQA # pylint: disable=broad-except # Catch Exception so that any error is still caught and the message is removed from the queue logger.error('Queue Error: %s', json.dumps(event_message), exc_info=True) + logger.error(e) diff --git a/queue_services/account-mailer/tests/conftest.py b/queue_services/account-mailer/tests/conftest.py index 5741230de7..de5ac74a1c 100644 --- a/queue_services/account-mailer/tests/conftest.py +++ b/queue_services/account-mailer/tests/conftest.py @@ -13,6 +13,7 @@ # limitations under the License. """Common setup and fixtures for the pytest suite used by this service.""" import asyncio +import logging import os import random import time @@ -20,6 +21,7 @@ import pytest from auth_api import db as _db +from auth_api.services.rest_service import RestService from flask import Flask from flask_migrate import Migrate, upgrade from nats.aio.client import Client as Nats @@ -29,6 +31,15 @@ from account_mailer.config import get_named_config +def setup_logging(conf): + """Create the services logger. + + TODO should be reworked to load in the proper loggers and remove others + """ + if conf and os.path.isfile(conf): + logging.config.fileConfig(conf) + + @contextmanager def not_raises(exception): """Corallary to the pytest raises builtin. @@ -48,6 +59,11 @@ def app(): _app = Flask(__name__) _app.config.from_object(get_named_config('testing')) _db.init_app(_app) + # Bypass caching. + + def get_service_token(): + pass + RestService.get_service_account_token = get_service_token return _app @@ -85,6 +101,8 @@ def db(app): # pylint: disable=redefined-outer-name, invalid-name Migrate(app, _db, directory=migration_path) upgrade() + # Restore the logging, alembic and sqlalchemy have their own logging from alembic.ini. + setup_logging(os.path.abspath('logging.conf')) return _db diff --git a/queue_services/account-mailer/tests/integration/test_worker_queue.py b/queue_services/account-mailer/tests/integration/test_worker_queue.py index 27fe06a748..fe554d6aca 100644 --- a/queue_services/account-mailer/tests/integration/test_worker_queue.py +++ b/queue_services/account-mailer/tests/integration/test_worker_queue.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. """Test Suite to ensure the worker routines are working as expected.""" +import types from datetime import datetime from unittest.mock import patch import pytest +from auth_api.services.rest_service import RestService from entity_queue_common.service_utils import subscribe_to_queue from account_mailer.enums import MessageType, SubjectType @@ -157,28 +159,34 @@ async def test_unlock_account_mailer_queue(app, session, stan_server, event_loop events_subject = 'test_subject' events_queue = 'test_queue' events_durable_name = 'test_durable' + response = types.SimpleNamespace() + response.status_code = 200 + response.content = bytes('foo', 'utf-8') + # patch RestService.post with patch.object(notification_service, 'send_email', return_value=None) as mock_send: # register the handler to test it - await subscribe_to_queue(events_stan, - events_subject, - events_queue, - events_durable_name, - cb_subscription_handler) - - # add an event to queue - mail_details = { - 'accountId': id, - 'accountName': org.name - } - await helper_add_event_to_queue(events_stan, events_subject, org_id=id, - msg_type=MessageType.NSF_UNLOCK_ACCOUNT.value, mail_details=mail_details) - - mock_send.assert_called - assert mock_send.call_args.args[0].get('recipients') == 'foo@bar.com' - assert mock_send.call_args.args[0].get('content').get('subject') == SubjectType.NSF_UNLOCK_ACCOUNT_SUBJECT.value - assert mock_send.call_args.args[0].get('attachments') is None - assert mock_send.call_args.args[0].get('content').get('body') is not None - assert True + with patch.object(RestService, 'post', return_value=response): + await subscribe_to_queue(events_stan, + events_subject, + events_queue, + events_durable_name, + cb_subscription_handler) + + # add an event to queue + mail_details = { + 'accountId': id, + 'accountName': org.name + } + await helper_add_event_to_queue(events_stan, events_subject, org_id=id, + msg_type=MessageType.NSF_UNLOCK_ACCOUNT.value, mail_details=mail_details) + + mock_send.assert_called + assert mock_send.call_args.args[0].get('recipients') == 'foo@bar.com' + assert mock_send.call_args.args[0].get('content').get('subject') == \ + SubjectType.NSF_UNLOCK_ACCOUNT_SUBJECT.value + assert mock_send.call_args.args[0].get('attachments') is None + assert mock_send.call_args.args[0].get('content').get('body') is not None + assert True @pytest.mark.asyncio