Skip to content

Commit

Permalink
17507 - Receipt Creation when NSF Account Paid and Unlocked (bcgov#2632)
Browse files Browse the repository at this point in the history
* Adding account_restored_nsf email processor

* Fixing typo

* Cleaning up linting

* Lint fixes

* lint fixes

* Import order

* Linting fixes

* Cleanup

* Cleanup

* Cleanup

* change enum, add in mock for report-api

* add in additional logging

* More logging

* Fix logging for unit tests

* Linting fixes

* Bypass service token

* Fix linting

---------

Co-authored-by: Travis Semple <[email protected]>
  • Loading branch information
rodrigo-barraza and seeker25 authored Jan 22, 2024
1 parent afcdc32 commit 78cdb1a
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 38 deletions.
10 changes: 8 additions & 2 deletions queue_services/account-mailer/logging.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[loggers]
keys=root,api
keys=root,api,asyncio

[handlers]
keys=console
Expand All @@ -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
Expand All @@ -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=
datefmt=
Original file line number Diff line number Diff line change
@@ -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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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: [[email protected]]([email protected])
4 changes: 2 additions & 2 deletions queue_services/account-mailer/src/account_mailer/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
23 changes: 18 additions & 5 deletions queue_services/account-mailer/src/account_mailer/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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)
18 changes: 18 additions & 0 deletions queue_services/account-mailer/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
# 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
from contextlib import contextmanager

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
Expand All @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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') == '[email protected]'
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') == '[email protected]'
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
Expand Down

0 comments on commit 78cdb1a

Please sign in to comment.