diff --git a/queue_services/business-emailer/src/business_emailer/email_processors/__init__.py b/queue_services/business-emailer/src/business_emailer/email_processors/__init__.py index 34e63c3b80..a85294c93f 100644 --- a/queue_services/business-emailer/src/business_emailer/email_processors/__init__.py +++ b/queue_services/business-emailer/src/business_emailer/email_processors/__init__.py @@ -81,8 +81,7 @@ def get_recipients(option: str, filing_json: dict, token: str | None = None, fil ): recipients = f"{recipients}, {', '.join(party_emails)}" - elif not is_coop: - # only add business email recipient for non-coop + else: recipients = get_recipient_from_auth(identifier, token) return recipients @@ -99,7 +98,7 @@ def get_recipient_from_auth(identifier: str, token: str) -> str: f'{current_app.config.get("AUTH_URL")}/entities/{identifier}', headers=headers ) - contacts = contact_info.json()["contacts"] + contacts = (contact_info.json()).get("contacts") if not contacts: current_app.logger.error("Queue Error: No email in business (%s) profile to send output to.", identifier, exc_info=True) @@ -111,7 +110,7 @@ def get_recipient_from_auth(identifier: str, token: str) -> str: def get_user_email_from_auth(user_name: str, token: str) -> str: """Get user email from auth.""" user_info = get_user_from_auth(user_name, token) - contacts = user_info.json()["contacts"] + contacts = (user_info.json()).get("contacts") if not contacts: return user_info.json().get("email") # idir user diff --git a/queue_services/business-emailer/src/business_emailer/email_processors/filing_notification.py b/queue_services/business-emailer/src/business_emailer/email_processors/filing_notification.py index 85bd6489cd..3c3248e848 100644 --- a/queue_services/business-emailer/src/business_emailer/email_processors/filing_notification.py +++ b/queue_services/business-emailer/src/business_emailer/email_processors/filing_notification.py @@ -14,6 +14,9 @@ """Email processing rules and actions corp filing notifications.""" from __future__ import annotations +import copy +from contextlib import suppress + from flask import current_app from jinja2 import Template @@ -42,36 +45,46 @@ def _get_additional_info(filing: Filing) -> dict: if filing.filing_type == "alteration": meta_data_alteration = filing.meta_data.get("alteration", {}) if filing.meta_data else {} additional_info["nameChange"] = "toLegalName" in meta_data_alteration + elif filing.filing_type == "specialResolution": + additional_info["nameChange"] = filing.filing_json["filing"].get("changeOfName") + additional_info["rulesChange"] = bool(filing.filing_json["filing"].get("alteration", {}).get("rulesFileKey")) return additional_info -def _get_additional_recipients(filing: Filing, token: str) -> str: +def _get_additional_recipients(filing: Filing, token: str) -> str | None: """Get additional recipients for a filing type.""" submitter_recipient_filings = ["alteration", "changeOfRegistration", "dissolution", "specialResolution"] if filing.filing_type in submitter_recipient_filings: - optional_email = filing.filing_json["filing"]["header"].get("documentOptionalEmail") - if filing.submitter_roles and UserRoles.staff in filing.submitter_roles and optional_email: + if filing.submitter_roles and UserRoles.staff in filing.submitter_roles: # when staff do filing documentOptionalEmail may contain completing party email - return optional_email + return filing.filing_json["filing"]["header"].get("documentOptionalEmail") else: return get_user_email_from_auth(filing.filing_submitter.username, token) def _get_attachments_and_extra_pdf_types(status: str, filing_type: str, filing: Filing, legal_type_key: str) -> tuple[list[str], list[str]]: """Get attachments for a filing type.""" - attachments = FILING_ATTACHMENTS.get(legal_type_key, {}).get(filing_type, {}).get("attachments", []) - extra_pdf_types = FILING_ATTACHMENTS.get(legal_type_key, {}).get(filing_type, {}).get("extraPdfTypes", []) + attachments = copy.deepcopy(FILING_ATTACHMENTS.get(legal_type_key, {}).get(filing_type, {}).get("attachments", [])) + extra_pdf_types = copy.deepcopy(FILING_ATTACHMENTS.get(legal_type_key, {}).get(filing_type, {}).get("extraPdfTypes", [])) + # filing sub type overrides attachments and extraPdfTypes if present - if filing.filing_sub_type and (attachments_sub := FILING_ATTACHMENTS.get(legal_type_key, {}).get(f"{filing_type}-{filing.filing_sub_type}", {})): + if filing.filing_sub_type and (attachments_sub := copy.deepcopy(FILING_ATTACHMENTS.get(legal_type_key, {}).get(f"{filing_type}-{filing.filing_sub_type}", {}))): attachments = attachments_sub.get("attachments", []) extra_pdf_types = attachments_sub.get("extraPdfTypes", []) - # filing attachments with change of name cert can override attachments and extraPdfTypes if present - if (_get_additional_info(filing).get("nameChange", False) - and (attachments_con := FILING_ATTACHMENTS.get(legal_type_key, {}).get(f"{filing_type}-con", {})) - ): - attachments = attachments_con.get("attachments", []) - extra_pdf_types = attachments_con.get("extraPdfTypes", []) + + # adjust attachments for some filings that have dynamic attachments based on the filing data + additional_info = _get_additional_info(filing) + with suppress(ValueError): + if not additional_info.get("nameChange"): + # remove con if in the attachments list + attachments.remove("Certificate of Name Change") + extra_pdf_types.remove("certificateOfNameChange") + + if not additional_info.get("rulesChange"): + # remove cr if in the attachments list + attachments.remove("Certified Rules") + extra_pdf_types.remove("certifiedRules") if status != Filing.Status.COMPLETED.value: extra_pdf_types = [] @@ -84,7 +97,7 @@ def _skip_email_check(status: str, filing: Filing, legal_type: str, filing_name: invalid_status = (status not in [Filing.Status.COMPLETED.value, Filing.Status.PAID.value] or (status == Filing.Status.PAID.value and not filing.is_future_effective)) invalid_data = not legal_type or not filing_name or not business_identifier - skipped_coop_filing_types = ["changeOfDirectors", "changeOfAddress"] + skipped_coop_filing_types = ["annualReport", "changeOfDirectors", "changeOfAddress"] invalid_coop_filing = legal_type == Business.LegalTypes.COOP.value and filing.filing_type in skipped_coop_filing_types return invalid_status or invalid_data or invalid_coop_filing diff --git a/queue_services/business-emailer/src/business_emailer/email_processors/special_resolution_notification.py b/queue_services/business-emailer/src/business_emailer/email_processors/special_resolution_notification.py deleted file mode 100644 index 467cb026a9..0000000000 --- a/queue_services/business-emailer/src/business_emailer/email_processors/special_resolution_notification.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright © 2023 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. -"""Email processing rules and actions for Special Resolution notifications.""" -from __future__ import annotations - -import re -from pathlib import Path - -from flask import current_app -from jinja2 import Template - -from business_emailer.email_processors import ( - get_filing_info, - get_recipient_from_auth, - get_user_email_from_auth, - substitute_template_parts, -) -from business_emailer.email_processors.special_resolution_helper import get_completed_pdfs, get_paid_pdfs -from business_model.models import Filing, UserRoles - - -def process(email_info: dict, token: str) -> dict: # pylint: disable=too-many-locals, too-many-branches - """Build the email for Special Resolution notification.""" - current_app.logger.debug("special_resolution_notification: %s", email_info) - # get template and fill in parts - filing_type, status = email_info["type"], email_info["option"] - # get template vars from filing - filing, business, leg_tmz_filing_date, leg_tmz_effective_date = get_filing_info(email_info["filingId"]) - filing_name = filing.filing_type[0].upper() + " ".join(re.findall("[a-zA-Z][^A-Z]*", filing.filing_type[1:])) - - template = Path( - f'{current_app.config.get("TEMPLATE_PATH")}/SR-CP-{status}.html' - ).read_text() - filled_template = substitute_template_parts(template) - # render template with vars - jnja_template = Template(filled_template, autoescape=True) - filing_data = (filing.json)["filing"][f"{filing_type}"] - name_changed = filing.filing_json["filing"].get("changeOfName") - rules_changed = bool(filing.filing_json["filing"].get("alteration", {}).get("rulesFileKey")) - html_out = jnja_template.render( - business=business, - filing=filing_data, - header=(filing.json)["filing"]["header"], - filing_date_time=leg_tmz_filing_date, - effective_date_time=leg_tmz_effective_date, - entity_dashboard_url=current_app.config.get("DASHBOARD_URL") + - (filing.json)["filing"]["business"].get("identifier", ""), - email_header=filing_name.upper(), - filing_type=filing_type, - name_changed=name_changed, - rules_updated=rules_changed - ) - - # get attachments - if status == Filing.Status.PAID.value: - pdfs = get_paid_pdfs(token, business, filing, leg_tmz_filing_date, leg_tmz_effective_date) - if status == Filing.Status.COMPLETED.value: - pdfs = get_completed_pdfs(token, business, filing, name_changed, rules_changed) - - # get recipients - identifier = filing.filing_json["filing"]["business"]["identifier"] - recipients = [] - recipients.append(get_recipient_from_auth(identifier, token)) - - if filing.submitter_roles and UserRoles.staff in filing.submitter_roles: - # when staff file a dissolution documentOptionalEmail may contain completing party email - recipients.append(filing.filing_json["filing"]["header"].get("documentOptionalEmail")) - else: - recipients.append(get_user_email_from_auth(filing.filing_submitter.username, token)) - - recipients = list(set(recipients)) - recipients = ", ".join(filter(None, recipients)).strip() - - # assign subject - subject = "" - if status == Filing.Status.PAID.value: - subject = "Confirmation of Special Resolution from the Business Registry" - elif status == Filing.Status.COMPLETED.value: - subject = "Special Resolution Documents from the Business Registry" - - legal_name = business.get("legalName", None) - subject = f"{legal_name} - {subject}" if legal_name else subject - - return { - "recipients": recipients, - "requestBy": "BCRegistries@gov.bc.ca", - "content": { - "subject": subject, - "body": f"{html_out}", - "attachments": pdfs - } - } diff --git a/queue_services/business-emailer/src/business_emailer/email_processors/util.py b/queue_services/business-emailer/src/business_emailer/email_processors/util.py index c60716da52..7af03e781f 100644 --- a/queue_services/business-emailer/src/business_emailer/email_processors/util.py +++ b/queue_services/business-emailer/src/business_emailer/email_processors/util.py @@ -37,6 +37,7 @@ "continuationIn": "Continuation Application", "incorporationApplication": "Incorporation Application", "registration": "Registration", + "specialResolution": "Special Resolution" } FILING_TITLE_SHORT = { @@ -62,14 +63,14 @@ "incorporationApplication": { "attachments": ["Incorporation Application","Certificate of Incorporation","Certified Rules","Memorandum","Receipt"], "extraPdfTypes": ["certificateOfIncorporation","certifiedRules","certifiedMemorandum"], - } + }, + "specialResolution": { + "attachments": ["Special Resolution","Special Resolution Application","Certificate of Name Change","Certified Rules","Receipt"], + "extraPdfTypes": ["specialResolutionApplication","certificateOfNameChange","certifiedRules"], + }, }, "CORP": { "alteration": { - "attachments": ["Alteration","Notice of Articles","Receipt"], - "extraPdfTypes": ["noticeOfArticles"], - }, - "alteration-con": { "attachments": ["Alteration","Notice of Articles","Certificate of Name Change","Receipt"], "extraPdfTypes": ["noticeOfArticles","certificateOfNameChange"], }, diff --git a/queue_services/business-emailer/src/business_emailer/email_templates/SR-CP-COMPLETED.html b/queue_services/business-emailer/src/business_emailer/email_templates/SR-CP-COMPLETED.html deleted file mode 100644 index eb41dfd1f6..0000000000 --- a/queue_services/business-emailer/src/business_emailer/email_templates/SR-CP-COMPLETED.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - Confirmation of Special Resolution - [[style.html]] - - - - - - - - - - diff --git a/queue_services/business-emailer/src/business_emailer/email_templates/SR-CP-PAID.html b/queue_services/business-emailer/src/business_emailer/email_templates/SR-CP-PAID.html deleted file mode 100644 index 44c13385d3..0000000000 --- a/queue_services/business-emailer/src/business_emailer/email_templates/SR-CP-PAID.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - You have successfully filed your special resolution with the BC Business Registry. - - [[style.html]] - - - - - - - - - - diff --git a/queue_services/business-emailer/src/business_emailer/email_templates/specialResolution.md b/queue_services/business-emailer/src/business_emailer/email_templates/specialResolution.md new file mode 100644 index 0000000000..fd13969376 --- /dev/null +++ b/queue_services/business-emailer/src/business_emailer/email_templates/specialResolution.md @@ -0,0 +1,13 @@ +# Your have successfully filed your special resolution with the BC Business Registry + +--- + +[[business-tombstone.md]] + +--- + +[[attachments.md]] + +--- + +[[business-registry-footer.md]] \ No newline at end of file diff --git a/queue_services/business-emailer/src/business_emailer/resources/business_emailer.py b/queue_services/business-emailer/src/business_emailer/resources/business_emailer.py index 9be2a3e0ef..e5dc2d6ccd 100644 --- a/queue_services/business-emailer/src/business_emailer/resources/business_emailer.py +++ b/queue_services/business-emailer/src/business_emailer/resources/business_emailer.py @@ -64,7 +64,6 @@ notice_of_withdrawal_notification, nr_notification, restoration_notification, - special_resolution_notification, ) from business_emailer.email_processors.util import FILING_TITLE from business_emailer.exceptions import EmailException, QueueException @@ -166,6 +165,7 @@ def send_email(email: dict, token: str): ) current_app.logger.debug("NOTIFY API response: %s", resp.status_code) if resp.status_code != HTTPStatus.OK: + current_app.logger.debug("NOTIFY API error: %s", resp.json()) raise EmailException except Exception: # this should log the error and put the email msg back on the queue @@ -256,9 +256,6 @@ def process_email(ce: SimpleCloudEvent): # pylint: disable=too-many-branches, t elif etype == "continuationOut" and option == Filing.Status.COMPLETED.value: email = continuation_out_notification.process(email_msg["email"], token) send_email(email, token) - elif etype == "specialResolution": - email = special_resolution_notification.process(email_msg["email"], token) - send_email(email, token) elif etype == "continuationIn" and option in ReviewStatus._member_names_: # Special case for review step of continuation in filing. Regular filing notifications are handled by the filing_notification processor. email = continuation_in_notification.process(email_msg["email"], token) @@ -279,7 +276,7 @@ def process_email(ce: SimpleCloudEvent): # pylint: disable=too-many-branches, t if email := filing_notification.process(email_msg["email"], token): send_email(email, token) else: - # should only be if this was for a coops filing + # should only be if this was for a coops filing or an invalid publish option current_app.logger.debug("No email to send for: %s", email_msg) else: current_app.logger.debug("No email to send for: %s", email_msg) diff --git a/queue_services/business-emailer/tests/unit/__init__.py b/queue_services/business-emailer/tests/unit/__init__.py index 482bd192c6..e63be0641a 100644 --- a/queue_services/business-emailer/tests/unit/__init__.py +++ b/queue_services/business-emailer/tests/unit/__init__.py @@ -67,10 +67,11 @@ # NB: These do not include the header or business in their templates FILING_TYPE_MAPPER = { + 'alteration': ALTERATION, 'annualReport': ANNUAL_REPORT['filing']['annualReport'], 'changeOfAddress': CORP_CHANGE_OF_ADDRESS, 'changeOfDirectors': CHANGE_OF_DIRECTORS, - 'alteration': ALTERATION + 'specialResolution': CP_SPECIAL_RESOLUTION_TEMPLATE['filing']['specialResolution'] } COMP_PARTY_EMAIL = 'comp_party@email.com' @@ -569,7 +570,7 @@ def prep_agm_extension_filing(identifier, payment_id, legal_type, legal_name): return filing -def prep_maintenance_filing(session, identifier, payment_id, status, filing_type, submitter_role=None): +def prep_maintenance_filing(session, identifier, payment_id, status, filing_type, submitter_role=None, template_overrides={}): """Return a new maintenance filing prepped for email notification.""" legal_type = Business.LegalTypes.COOP.value if identifier.startswith('CP') else Business.LegalTypes.BCOMP.value business = create_business(identifier, legal_type, LEGAL_NAME) @@ -581,6 +582,13 @@ def prep_maintenance_filing(session, identifier, payment_id, status, filing_type if submitter_role: filing_template['filing']['header']['documentOptionalEmail'] = f'{submitter_role}@email.com' + + filing_template['filing'] = { + **filing_template['filing'], + # add any extra data or overwrite as required + **template_overrides + } + filing = create_filing(token=payment_id, filing_json=filing_template, business_id=business.id) user = create_user('test_user') @@ -666,33 +674,23 @@ def prep_firm_correction_filing(session, identifier, payment_id, legal_type, leg return filing -def prep_cp_special_resolution_filing(identifier, payment_id, legal_type, legal_name, submitter_role=None): - """Return a new cp special resolution out filing prepped for email notification.""" - business = create_business(identifier, legal_type=legal_type, legal_name=legal_name) - filing_template = copy.deepcopy(CP_SPECIAL_RESOLUTION_TEMPLATE) - filing_template['filing']['business'] = \ - {'identifier': f'{identifier}', 'legalype': legal_type, 'legalName': legal_name} - filing_template['filing']['alteration'] = { - 'business': { - 'identifier': 'BC1234567', - 'legalType': 'BEN' - }, - 'contactPoint': { - 'email': 'joe@email.com' - }, - 'rulesInResolution': True, - 'rulesFileKey': 'cooperative/a8abe1a6-4f45-4105-8a05-822baee3b743.pdf' - } - if submitter_role: - filing_template['filing']['header']['documentOptionalEmail'] = f'{submitter_role}@email.com' - filing = create_filing(token=payment_id, filing_json=filing_template, business_id=business.id) - - user = create_user('cp_test_user') - filing.submitter_id = user.id - if submitter_role: - filing.submitter_roles = submitter_role - filing.save() - return filing +def prep_special_resolution_filing(session, identifier='CP1234567', submitter_role=None, has_name_change=False, has_rule_change=False): + """Return a new special resolution out filing prepped for email notification.""" + filing_template_overrides = {} + if has_name_change: + filing_template_overrides['changeOfName'] = { + 'nameRequest': { + 'nrNumber': 'NR 8798956', + 'legalName': 'HAULER MEDIA INC.', + 'legalType': 'BC' + } + } + if has_rule_change: + filing_template_overrides['alteration'] = { + 'rulesInResolution': True, + 'rulesFileKey': 'cooperative/a8abe1a6-4f45-4105-8a05-822baee3b743.pdf' + } + return prep_maintenance_filing(session, identifier, '1', 'COMPLETED', 'specialResolution', submitter_role, filing_template_overrides) def prep_cp_special_resolution_correction_filing(session, business, original_filing_id, diff --git a/queue_services/business-emailer/tests/unit/email_processors/test_correction_notification.py b/queue_services/business-emailer/tests/unit/email_processors/test_correction_notification.py index 6d3e5b8e1a..ce6e398765 100644 --- a/queue_services/business-emailer/tests/unit/email_processors/test_correction_notification.py +++ b/queue_services/business-emailer/tests/unit/email_processors/test_correction_notification.py @@ -21,12 +21,13 @@ from business_emailer.email_processors import correction_notification from tests.unit import ( + LEGAL_NAME, prep_cp_special_resolution_correction_filing, prep_cp_special_resolution_correction_upload_memorandum_filing, - prep_cp_special_resolution_filing, prep_firm_correction_filing, prep_incorp_filing, prep_incorporation_correction_filing, + prep_special_resolution_filing, ) @@ -127,8 +128,8 @@ def test_bc_correction_notification(app, session, status, legal_type): def test_cp_special_resolution_correction_notification(app, session, status, legal_type): """Assert that email attributes are correct.""" # setup filing + business for email - legal_name = 'cp business' - original_filing = prep_cp_special_resolution_filing(CP_IDENTIFIER, '1', legal_type, legal_name, submitter_role=None) + legal_name = LEGAL_NAME + original_filing = prep_special_resolution_filing(session, CP_IDENTIFIER, submitter_role=None) token = 'token' business = Business.find_by_identifier(CP_IDENTIFIER) filing = prep_cp_special_resolution_correction_filing(session, business, original_filing.id, @@ -159,11 +160,9 @@ def test_cp_special_resolution_correction_notification(app, session, status, leg def test_complete_special_resolution_correction_attachments(session, config): """Test completed special resolution correction notification.""" # setup filing + business for email - legal_type = Business.LegalTypes.COOP.value - legal_name = 'test cp sr business' token = 'token' status = 'COMPLETED' - original_filing = prep_cp_special_resolution_filing(CP_IDENTIFIER, '1', legal_type, legal_name, submitter_role=None) + original_filing = prep_special_resolution_filing(session, CP_IDENTIFIER, submitter_role=None) business = Business.find_by_identifier(CP_IDENTIFIER) filing = prep_cp_special_resolution_correction_filing(session, business, original_filing.id, '1', status, SPECIAL_RESOLUTION_FILING_TYPE) @@ -209,11 +208,9 @@ def test_complete_special_resolution_correction_attachments(session, config): def test_paid_special_resolution_correction_attachments(session, config): """Test paid special resolution correction notification.""" # setup filing + business for email - legal_type = Business.LegalTypes.COOP.value - legal_name = 'test cp sr business' token = 'token' status = 'PAID' - original_filing = prep_cp_special_resolution_filing(CP_IDENTIFIER, '1', legal_type, legal_name, submitter_role=None) + original_filing = prep_special_resolution_filing(session, CP_IDENTIFIER, submitter_role=None) business = Business.find_by_identifier(CP_IDENTIFIER) filing = prep_cp_special_resolution_correction_filing(session, business, original_filing.id, '1', status, SPECIAL_RESOLUTION_FILING_TYPE) @@ -250,9 +247,8 @@ def test_paid_special_resolution_correction_attachments(session, config): def test_paid_special_resolution_correction_on_correction(session, config, legal_type, filing_type): """Assert that email attributes are correct.""" # setup filing + business for email - legal_name = 'cp business' - original_filing = prep_cp_special_resolution_filing(CP_IDENTIFIER, '1', legal_type, - legal_name, submitter_role=None) + legal_name = LEGAL_NAME + original_filing = prep_special_resolution_filing(session, CP_IDENTIFIER, submitter_role=None) token = 'token' business = Business.find_by_identifier(CP_IDENTIFIER) filing_correction = prep_cp_special_resolution_correction_filing(session, business, original_filing.id, @@ -279,11 +275,8 @@ def test_complete_special_resolution_correction_memorandum_attachments(session, Memorandum (3 attachments). The point of this test is to exercise the `if memorandum_changed:` branch in special_resolution_helper.get_completed_pdfs. """ - legal_type = Business.LegalTypes.COOP.value - legal_name = 'test cp sr business' token = 'token' - original_filing = prep_cp_special_resolution_filing( - CP_IDENTIFIER, '1', legal_type, legal_name, submitter_role=None) + original_filing = prep_special_resolution_filing(session, CP_IDENTIFIER, submitter_role=None) business = Business.find_by_identifier(CP_IDENTIFIER) filing = prep_cp_special_resolution_correction_upload_memorandum_filing( session, business, original_filing.id, '1', 'COMPLETED', SPECIAL_RESOLUTION_FILING_TYPE) diff --git a/queue_services/business-emailer/tests/unit/email_processors/test_cp_special_resolution_notification.py b/queue_services/business-emailer/tests/unit/email_processors/test_cp_special_resolution_notification.py deleted file mode 100644 index ca5c719a94..0000000000 --- a/queue_services/business-emailer/tests/unit/email_processors/test_cp_special_resolution_notification.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright © 2021 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. -"""The Unit Tests for the Special Resolution email processor.""" -import base64 -from unittest.mock import patch - -import pytest -import requests_mock -from business_model.models import Business - -from business_emailer.email_processors import special_resolution_notification -from tests.unit import prep_cp_special_resolution_filing - - -LEGAL_TYPE = Business.LegalTypes.COOP.value -LEGAL_NAME = 'test business' -IDENTIFIER = 'CP1234567' -TOKEN = 'token' -RECIPIENT_EMAIL = 'recipient@email.com' -USER_EMAIL_FROM_AUTH = 'user@email.com' - - -@pytest.mark.parametrize('status', [ - ('PAID'), - ('COMPLETED') -]) -def test_cp_special_resolution_notification(session, app, config, status): - """Assert that the special resolution email processor works as expected.""" - # setup filing + business for email - filing = prep_cp_special_resolution_filing(IDENTIFIER, '1', LEGAL_TYPE, LEGAL_NAME, submitter_role=None) - get_pdf_function = 'get_paid_pdfs' if status == 'PAID' else 'get_completed_pdfs' - # test processor - with patch.object(special_resolution_notification, get_pdf_function, return_value=[]) as mock_get_pdfs: - with patch.object(special_resolution_notification, 'get_recipient_from_auth', - return_value=RECIPIENT_EMAIL): - with patch.object(special_resolution_notification, 'get_user_email_from_auth', - return_value=USER_EMAIL_FROM_AUTH): - email = special_resolution_notification.process( - {'filingId': filing.id, 'type': 'specialResolution', 'option': status}, TOKEN) - if status == 'PAID': - assert email['content']['subject'] == LEGAL_NAME + \ - ' - Confirmation of Special Resolution from the Business Registry' - else: - assert email['content']['subject'] == \ - LEGAL_NAME + ' - Special Resolution Documents from the Business Registry' - - assert RECIPIENT_EMAIL in email['recipients'] - assert USER_EMAIL_FROM_AUTH in email['recipients'] - assert email['content']['body'] - assert email['content']['attachments'] == [] - assert mock_get_pdfs.call_args[0][0] == TOKEN - assert mock_get_pdfs.call_args[0][1]['identifier'] == IDENTIFIER - assert mock_get_pdfs.call_args[0][2] == filing - - -def test_complete_special_resolution_attachments(session, config): - """Test completed special resolution notification.""" - # setup filing + business for email - status = 'COMPLETED' - filing = prep_cp_special_resolution_filing(IDENTIFIER, '1', LEGAL_TYPE, LEGAL_NAME, submitter_role=None) - with requests_mock.Mocker() as m: - with patch.object(special_resolution_notification, 'get_recipient_from_auth', - return_value=RECIPIENT_EMAIL): - with patch.object(special_resolution_notification, 'get_user_email_from_auth', - return_value=USER_EMAIL_FROM_AUTH): - m.get( - ( - f'{config.get("LEGAL_API_URL")}' - f'/businesses/{IDENTIFIER}' - f'/filings/{filing.id}' - f'/documents/specialResolution' - ), - content=b'pdf_content_1', - status_code=200 - ) - m.get( - f'{config.get("LEGAL_API_URL")}/businesses/{IDENTIFIER}/filings/{filing.id}' - '/documents/certificateOfNameChange', - content=b'pdf_content_2', - status_code=200 - ) - m.get( - f'{config.get("LEGAL_API_URL")}/businesses/{IDENTIFIER}/filings/{filing.id}' - '/documents/certifiedRules', - content=b'pdf_content_3', - status_code=200 - ) - - output = special_resolution_notification.process({ - 'filingId': filing.id, - 'type': 'specialResolution', - 'option': status - }, TOKEN) - assert 'content' in output - assert 'attachments' in output['content'] - assert len(output['content']['attachments']) == 3 - assert output['content']['attachments'][0]['fileName'] == 'Special Resolution.pdf' - assert (base64.b64decode(output['content']['attachments'][0]['fileBytes']).decode('utf-8') - == 'pdf_content_1') - assert output['content']['attachments'][1]['fileName'] == 'Certificate of Name Change.pdf' - assert (base64.b64decode(output['content']['attachments'][1]['fileBytes']).decode('utf-8') - == 'pdf_content_2') - assert output['content']['attachments'][2]['fileName'] == 'Certified Rules.pdf' - assert (base64.b64decode(output['content']['attachments'][2]['fileBytes']).decode('utf-8') - == 'pdf_content_3') - - -def test_paid_special_resolution_attachments(session, config): - """Test paid special resolution notification.""" - # setup filing + business for email - status = 'PAID' - filing = prep_cp_special_resolution_filing(IDENTIFIER, '1', LEGAL_TYPE, LEGAL_NAME, submitter_role=None) - with requests_mock.Mocker() as m: - with patch.object(special_resolution_notification, 'get_recipient_from_auth', - return_value=RECIPIENT_EMAIL): - with patch.object(special_resolution_notification, 'get_user_email_from_auth', - return_value=USER_EMAIL_FROM_AUTH): - m.get( - ( - f'{config.get("LEGAL_API_URL")}' - f'/businesses/{IDENTIFIER}' - f'/filings/{filing.id}' - f'/documents/specialResolutionApplication' - ), - content=b'pdf_content_1', - status_code=200 - ) - m.post( - f'{config.get("PAY_API_URL")}/1/receipts', - content=b'pdf_content_2', - status_code=201 - ) - output = special_resolution_notification.process({ - 'filingId': filing.id, - 'type': 'specialResolution', - 'option': status - }, TOKEN) - assert 'content' in output - assert 'attachments' in output['content'] - assert len(output['content']['attachments']) == 2 - assert output['content']['attachments'][0]['fileName'] == 'Special Resolution Application.pdf' - assert (base64.b64decode(output['content']['attachments'][0]['fileBytes']).decode('utf-8') - == 'pdf_content_1') - assert output['content']['attachments'][1]['fileName'] == 'Receipt.pdf' - assert (base64.b64decode(output['content']['attachments'][1]['fileBytes']).decode('utf-8') - == 'pdf_content_2') diff --git a/queue_services/business-emailer/tests/unit/email_processors/test_email_processors_init.py b/queue_services/business-emailer/tests/unit/email_processors/test_email_processors_init.py index 7ab0bf6afc..e79f9b8f88 100644 --- a/queue_services/business-emailer/tests/unit/email_processors/test_email_processors_init.py +++ b/queue_services/business-emailer/tests/unit/email_processors/test_email_processors_init.py @@ -326,23 +326,8 @@ def test_get_recipients_missing_contact_point_coalesced_to_empty_string(app): assert result == '' -def test_get_recipients_coop_identifier_skips_auth_call(app): - """Assert that CP (coop) identifiers return empty string without calling get_recipient_from_auth.""" - filing_json = { - 'filing': { - 'header': {'name': 'annualReport'}, - 'business': {'identifier': 'CP1234567'}, - } - } - with app.app_context(): - with patch('business_emailer.email_processors.get_recipient_from_auth') as mock_auth: - result = get_recipients('COMPLETED', filing_json, token='token', filing_type='annualReport') - assert result == '' - mock_auth.assert_not_called() - - -def test_get_recipients_non_coop_identifier_calls_get_recipient_from_auth(app): - """Assert that non-coop identifiers call get_recipient_from_auth for the business email.""" +def test_get_recipients_calls_get_recipient_from_auth(app): + """Assert that get_recipients calls get_recipient_from_auth for the business email.""" filing_json = { 'filing': { 'header': {'name': 'annualReport'}, diff --git a/queue_services/business-emailer/tests/unit/email_processors/test_filing_notification.py b/queue_services/business-emailer/tests/unit/email_processors/test_filing_notification.py index 4315a4b0b1..64269614e8 100644 --- a/queue_services/business-emailer/tests/unit/email_processors/test_filing_notification.py +++ b/queue_services/business-emailer/tests/unit/email_processors/test_filing_notification.py @@ -21,7 +21,7 @@ from business_emailer.email_processors import filing_notification from tests.unit import (CONTACT_POINT, LEGAL_NAME, PARTY_EMAIL_1, PARTY_EMAIL_2, prep_bootstrap_filing, prep_change_of_registration_filing, prep_incorp_filing, - prep_maintenance_filing, prep_registration_filing) + prep_maintenance_filing, prep_registration_filing, prep_special_resolution_filing) from tests.unit.helpers import make_future_effective, make_non_future_effective @@ -323,7 +323,7 @@ def test_business_number_rendering(app, session, mock_pdfs, filing_type, legal_t # --------------------------------------------------------------------------- -# tests for non firm maintenance filings (changeOfAddress, changeOfDirectors, alteration, annualReport) +# tests for non firm maintenance filings (changeOfAddress, changeOfDirectors, alteration, annualReport, etc.) # --------------------------------------------------------------------------- @pytest.mark.parametrize(['status', 'filing_type', 'submitter_role'], [ @@ -333,7 +333,9 @@ def test_business_number_rendering(app, session, mock_pdfs, filing_type, legal_t ('COMPLETED', 'changeOfAddress', None), ('COMPLETED', 'changeOfDirectors', None), ('COMPLETED', 'alteration', None), - ('COMPLETED', 'alteration', 'staff') + ('COMPLETED', 'alteration', 'staff'), + ('COMPLETED', 'specialResolution', None), + ('COMPLETED', 'specialResolution', 'staff'), ]) def test_maintenance_notification(app, session, status, filing_type, submitter_role, mock_pdfs, mock_recipients, mock_user_email): @@ -421,6 +423,51 @@ def test_filing_attachments_alteration_completed_name_change(session, config, mo assert_attachment(attachments[3], 'Receipt.pdf', 'pdf_content_4') +@pytest.mark.parametrize('has_name_change, has_rule_change, expected_attachments', [ + (False, False, [ + {'fileName': 'Special Resolution.pdf', 'content': 'pdf_content_1', 'order': '1'}, + {'fileName': 'Special Resolution Application.pdf', 'content': 'pdf_content_2', 'order': '2'}, + {'fileName': 'Receipt.pdf', 'content': 'pdf_content_5', 'order': '3'}, + ]), + (True, False, [ + {'fileName': 'Special Resolution.pdf', 'content': 'pdf_content_1', 'order': '1'}, + {'fileName': 'Special Resolution Application.pdf', 'content': 'pdf_content_2', 'order': '2'}, + {'fileName': 'Certificate of Name Change.pdf', 'content': 'pdf_content_3', 'order': '3'}, + {'fileName': 'Receipt.pdf', 'content': 'pdf_content_5', 'order': '4'}, + ]), + (False, True, [ + {'fileName': 'Special Resolution.pdf', 'content': 'pdf_content_1', 'order': '1'}, + {'fileName': 'Special Resolution Application.pdf', 'content': 'pdf_content_2', 'order': '2'}, + {'fileName': 'Certified Rules.pdf', 'content': 'pdf_content_4', 'order': '3'}, + {'fileName': 'Receipt.pdf', 'content': 'pdf_content_5', 'order': '4'}, + ]), + (True, True, [ + {'fileName': 'Special Resolution.pdf', 'content': 'pdf_content_1', 'order': '1'}, + {'fileName': 'Special Resolution Application.pdf', 'content': 'pdf_content_2', 'order': '2'}, + {'fileName': 'Certificate of Name Change.pdf', 'content': 'pdf_content_3', 'order': '3'}, + {'fileName': 'Certified Rules.pdf', 'content': 'pdf_content_4', 'order': '4'}, + {'fileName': 'Receipt.pdf', 'content': 'pdf_content_5', 'order': '5'}, + ]), +], ids=['No name or rule changes', 'Name change included', 'Rule change included', 'Name and Rule change included']) +def test_filing_attachments_special_resolution(session, config, mock_recipients, mock_user_email, has_name_change, has_rule_change, expected_attachments): + """special resolution COMPLETED cases.""" + identifier = 'CP1234567' + filing = prep_special_resolution_filing(session, has_name_change=has_name_change, has_rule_change=has_rule_change) + with requests_mock.Mocker() as m: + mock_filing_docs(m, config, identifier, filing, { + 'specialResolution': b'pdf_content_1', + 'specialResolutionApplication': b'pdf_content_2', + 'certificateOfNameChange': b'pdf_content_3', + 'certifiedRules': b'pdf_content_4', + }, receipt=b'pdf_content_5') + output = process_filing(filing, 'specialResolution', 'COMPLETED') + + attachments = output['content']['attachments'] + assert len(attachments) == len(expected_attachments) + for attachment, expected in zip(attachments, expected_attachments): + assert_attachment(attachment, expected['fileName'], expected['content'], expected['order']) + + @pytest.mark.parametrize(['filing_type', 'status', 'expected_header', 'expected_subject'], [ ( 'alteration', diff --git a/queue_services/business-emailer/tests/unit/test_worker.py b/queue_services/business-emailer/tests/unit/test_worker.py index e41aea0fed..265f386816 100644 --- a/queue_services/business-emailer/tests/unit/test_worker.py +++ b/queue_services/business-emailer/tests/unit/test_worker.py @@ -29,7 +29,6 @@ filing_notification, name_request, nr_notification, - special_resolution_notification, ) from business_emailer.exceptions import EmailException, QueueException from business_emailer.resources import business_emailer as worker @@ -37,13 +36,14 @@ from tests import MockResponse from tests.unit import ( CONTACT_POINT, + LEGAL_NAME, create_business, create_furnishing, prep_bootstrap_filing, prep_cp_special_resolution_correction_filing, - prep_cp_special_resolution_filing, prep_incorp_filing, prep_maintenance_filing, + prep_special_resolution_filing ) from tests.unit.helpers import make_future_effective, make_non_future_effective @@ -126,17 +126,18 @@ def test_process_incorp_email_paid_non_future_no_email_sent(app, session, mocker assert not mock_send_email.call_args -@pytest.mark.parametrize(['status', 'filing_type'], [ - ('PAID', 'changeOfAddress'), - ('COMPLETED', 'alteration'), - ('COMPLETED', 'annualReport'), - ('COMPLETED', 'changeOfAddress'), - ('COMPLETED', 'changeOfDirectors') +@pytest.mark.parametrize(['status', 'filing_type', 'identifier', 'submitter_role'], [ + ('PAID', 'changeOfAddress', 'BC1234567', None), + ('COMPLETED', 'alteration', 'BC1234567', None), + ('COMPLETED', 'annualReport', 'BC1234567', None), + ('COMPLETED', 'changeOfAddress', 'BC1234567', None), + ('COMPLETED', 'specialResolution', 'CP1234567', None), + ('COMPLETED', 'specialResolution', 'CP1234567', 'staff'), ]) -def test_maintenance_notification(app, session, status, filing_type): - """Assert that the legal name is changed.""" +def test_maintenance_notification(app, session, status, filing_type, identifier, submitter_role): + """Assert that valid maintenance filing cases send an email.""" # setup filing + business for email - filing = prep_maintenance_filing(session, 'BC1234567', '1', status, filing_type) + filing = prep_maintenance_filing(session, identifier, '1', status, filing_type, submitter_role) if status == 'PAID': make_future_effective(filing) token = 'token' @@ -154,9 +155,9 @@ def test_maintenance_notification(app, session, status, filing_type): ) assert mock_get_pdfs.call_args[0][0] == token - - assert mock_get_pdfs.call_args[0][1]['identifier'] == 'BC1234567' - assert mock_get_pdfs.call_args[0][1]['legalType'] == Business.LegalTypes.BCOMP.value + assert mock_get_pdfs.call_args[0][1]['identifier'] == identifier + business: Business = Business.find_by_identifier(identifier) + assert mock_get_pdfs.call_args[0][1]['legalType'] == business.legal_type assert mock_get_pdfs.call_args[0][1]['legalName'] == 'test business' assert mock_get_pdfs.call_args[0][2] == filing @@ -166,6 +167,8 @@ def test_maintenance_notification(app, session, status, filing_type): assert mock_send_email.call_args[0][0]['content']['subject'] assert 'test@test.com' in mock_send_email.call_args[0][0]['recipients'] + if submitter_role: + assert 'staff@email.com' in mock_send_email.call_args[0][0]['recipients'] assert mock_send_email.call_args[0][0]['content']['body'] assert mock_send_email.call_args[0][0]['content']['attachments'] == [] assert mock_send_email.call_args[0][1] == token @@ -175,11 +178,13 @@ def test_maintenance_notification(app, session, status, filing_type): ('PAID', 'annualReport', 'BC1234567'), ('PAID', 'changeOfAddress', 'CP1234567'), ('PAID', 'changeOfDirectors', 'CP1234567'), + ('PAID', 'specialResolution', 'BC1234567'), + ('COMPLETED', 'annualReport', 'CP1234567'), ('COMPLETED', 'changeOfAddress', 'CP1234567'), ('COMPLETED', 'changeOfDirectors', 'CP1234567') ]) def test_skips_notification(app, session, status, filing_type, identifier): - """Assert that the legal name is changed.""" + """Assert that the emailer skips sending an email for invalid cases.""" # setup filing + business for email filing = prep_maintenance_filing(session, identifier, '1', status, filing_type) token = 'token' @@ -219,49 +224,6 @@ def test_process_mras_email(app, session): assert mock_send_email.call_args[0][1] == token -@pytest.mark.parametrize(['option', 'submitter_role'], [ - ('PAID', 'staff'), - ('COMPLETED', None), -]) -def test_process_special_resolution_email(app, session, option, submitter_role): - """Assert that an special resolution email msg is processed correctly.""" - filing = prep_cp_special_resolution_filing('CP1234567', '1', 'CP', 'TEST', submitter_role=submitter_role) - token = '1' - get_pdf_function = 'get_paid_pdfs' if option == 'PAID' else 'get_completed_pdfs' - # test worker - with patch.object(AccountService, 'get_bearer_token', return_value=token): - with patch.object(special_resolution_notification, get_pdf_function, return_value=[]) as mock_get_pdfs: - with patch.object(special_resolution_notification, 'get_recipient_from_auth', - return_value='recipient@email.com'): - with patch.object(special_resolution_notification, 'get_user_email_from_auth', - return_value='user@email.com'): - with patch.object(worker, 'send_email', return_value='success') as mock_send_email: - worker.process_email( - SimpleCloudEvent( - data={'email': {'filingId': filing.id, 'type': 'specialResolution', 'option': option}} - ) - ) - - assert mock_get_pdfs.call_args[0][0] == token - assert mock_get_pdfs.call_args[0][1]['identifier'] == 'CP1234567' - assert mock_get_pdfs.call_args[0][2] == filing - - if option == 'PAID': - assert mock_send_email.call_args[0][0]['content']['subject'] == \ - 'TEST - Confirmation of Special Resolution from the Business Registry' - else: - assert mock_send_email.call_args[0][0]['content']['subject'] == \ - 'TEST - Special Resolution Documents from the Business Registry' - assert 'recipient@email.com' in mock_send_email.call_args[0][0]['recipients'] - if submitter_role: - assert f'{submitter_role}@email.com' in mock_send_email.call_args[0][0]['recipients'] - else: - assert 'user@email.com' in mock_send_email.call_args[0][0]['recipients'] - assert mock_send_email.call_args[0][0]['content']['body'] - assert mock_send_email.call_args[0][0]['content']['attachments'] == [] - assert mock_send_email.call_args[0][1] == token - - @pytest.mark.parametrize('option', [ ('PAID'), ('COMPLETED'), @@ -269,7 +231,7 @@ def test_process_special_resolution_email(app, session, option, submitter_role): def test_process_correction_cp_sr_email(app, session, option): """Assert that a correction email msg is processed correctly.""" identifier = 'CP1234567' - original_filing = prep_cp_special_resolution_filing(identifier, '1', 'CP', 'TEST', submitter_role=None) + original_filing = prep_special_resolution_filing(session, identifier, submitter_role=None) token = '1' business = Business.find_by_identifier(identifier) filing = prep_cp_special_resolution_correction_filing(session, business, original_filing.id, @@ -286,10 +248,10 @@ def test_process_correction_cp_sr_email(app, session, option): if option == 'PAID': assert mock_send_email.call_args[0][0]['content']['subject'] == \ - 'TEST - Confirmation of correction' + f'{LEGAL_NAME} - Confirmation of correction' else: assert mock_send_email.call_args[0][0]['content']['subject'] == \ - 'TEST - Correction Documents from the Business Registry' + f'{LEGAL_NAME} - Correction Documents from the Business Registry' assert 'cp_sr@test.com' in mock_send_email.call_args[0][0]['recipients'] assert mock_send_email.call_args[0][0]['content']['body'] assert mock_send_email.call_args[0][0]['content']['attachments'] == [] diff --git a/queue_services/business-emailer/tests/unit/test_worker_dispatch.py b/queue_services/business-emailer/tests/unit/test_worker_dispatch.py index bafb30c3cf..cc1cb4f825 100644 --- a/queue_services/business-emailer/tests/unit/test_worker_dispatch.py +++ b/queue_services/business-emailer/tests/unit/test_worker_dispatch.py @@ -42,7 +42,6 @@ notice_of_withdrawal_notification, nr_notification, restoration_notification, - special_resolution_notification, ) from business_emailer.resources import business_emailer as worker from business_emailer.services import flags @@ -305,16 +304,6 @@ def test_continuation_out_completed_dispatches(app, session, mocker, mock_send_e mock_send_email.assert_called_once_with(STUB_EMAIL, TOKEN) -def test_special_resolution_dispatches(app, session, mocker, mock_send_email): - mock_process = mocker.patch.object(special_resolution_notification, "process", return_value=STUB_EMAIL) - email = {"type": "specialResolution", "option": "PAID"} - - worker.process_email(_ce({"email": email})) - - mock_process.assert_called_once_with(email, TOKEN) - mock_send_email.assert_called_once_with(STUB_EMAIL, TOKEN) - - def test_intent_to_liquidate_dispatches(app, session, mocker, mock_send_email): mock_process = mocker.patch.object(intent_to_liquidate_notification, "process", return_value=STUB_EMAIL) email = {"type": "intentToLiquidate", "option": COMPLETED} @@ -369,6 +358,7 @@ def test_cease_receiver_completed_dispatches(app, session, mocker, mock_send_ema "continuationIn", "incorporationApplication", "registration", + "specialResolution" ]) def test_filing_notification_dispatches(app, session, mocker, mock_send_email, filing_type): """Assert that the filing_notification branch works as expected."""