diff --git a/jobs/payment-jobs/tasks/cfs_create_account_task.py b/jobs/payment-jobs/tasks/cfs_create_account_task.py index 0c779c4e3..6e3a5535a 100644 --- a/jobs/payment-jobs/tasks/cfs_create_account_task.py +++ b/jobs/payment-jobs/tasks/cfs_create_account_task.py @@ -13,9 +13,10 @@ # limitations under the License. """Task to create CFS account offline.""" import re -from datetime import datetime, timezone +from datetime import datetime, time, timezone from typing import Dict +import pytz from flask import current_app from pay_api.models import CfsAccount as CfsAccountModel from pay_api.models import PaymentAccount as PaymentAccountModel @@ -34,6 +35,26 @@ class CreateAccountTask: # pylint: disable=too-few-public-methods """Create CFS Account.""" + @staticmethod + def is_within_cfs_business_hours() -> bool: + """Check if the current time is within business hours.""" + # https://bcgov.sharepoint.com/sites/FIN-OCG-CAS-CFS/SitePages/Calendar.aspx + vancouver_tz = pytz.timezone("America/Vancouver") + now_vancouver = datetime.now(vancouver_tz) + day_of_week = now_vancouver.weekday() + weekday_hours = (time(6, 0), time(21, 0)) # Monday-Friday: 6am - 9pm + saturday_hours = (time(6, 0), time(19, 0)) # Saturday: 6am - 7pm + sunday_hours = (time(9, 0), time(21, 0)) # Sunday: 9am - 9pm + current_time = now_vancouver.time() + # Check business hours based on the day + if day_of_week in range(0, 5): + return weekday_hours[0] <= current_time <= weekday_hours[1] + if day_of_week == 5: + return saturday_hours[0] <= current_time <= saturday_hours[1] + if day_of_week == 6: + return sunday_hours[0] <= current_time <= sunday_hours[1] + return False + @classmethod def create_accounts(cls): # pylint: disable=too-many-locals """Find all pending accounts to be created in CFS. @@ -43,6 +64,10 @@ def create_accounts(cls): # pylint: disable=too-many-locals 2. Create CFS accounts. 3. Publish a message to the queue if successful. """ + if not CreateAccountTask.is_within_cfs_business_hours(): + current_app.logger.info("Outside business hours. Skipping account creation.") + return + # Pass payment method if offline account creation has be restricted based on payment method. pending_accounts = CfsAccountModel.find_all_pending_accounts() current_app.logger.info(f"Found {len(pending_accounts)} CFS Accounts to be created.") diff --git a/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py b/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py index 1a7f20581..501dcdee4 100644 --- a/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py +++ b/jobs/payment-jobs/tests/jobs/test_activate_pad_account_task.py @@ -27,6 +27,7 @@ from tasks.cfs_create_account_task import CreateAccountTask from .factory import factory_create_pad_account +from .utils import valid_time_for_job def test_activate_pad_accounts(session): @@ -39,7 +40,8 @@ def test_activate_pad_accounts_with_time_check(session): """Test Activate account.""" # Create a pending account first, then call the job account = factory_create_pad_account(auth_account_id="1") - CreateAccountTask.create_accounts() + with freeze_time(valid_time_for_job): + CreateAccountTask.create_accounts() account: PaymentAccount = PaymentAccount.find_by_id(account.id) cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value, "Created account has pending pad status" @@ -66,7 +68,9 @@ def test_activate_bcol_change_to_pad(session): """Test Activate account.""" # Create a pending account first, then call the job account = factory_create_pad_account(auth_account_id="1", payment_method=PaymentMethod.DRAWDOWN.value) - CreateAccountTask.create_accounts() + + with freeze_time(valid_time_for_job): + CreateAccountTask.create_accounts() account = PaymentAccount.find_by_id(account.id) cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value, "Created account has pending pad status" diff --git a/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py b/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py index 3e974eaee..3aade65f5 100644 --- a/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py +++ b/jobs/payment-jobs/tests/jobs/test_cfs_create_account_task.py @@ -16,9 +16,12 @@ Test-Suite to ensure that the CreateAccountTask is working as expected. """ +from datetime import datetime from unittest.mock import patch +import pytz import requests +from freezegun import freeze_time from pay_api.models import CfsAccount, PaymentAccount from pay_api.services.online_banking_service import OnlineBankingService from pay_api.services.pad_service import PadService @@ -29,157 +32,179 @@ from utils import mailer from .factory import factory_create_eft_account, factory_create_online_banking_account, factory_create_pad_account +from .utils import invalid_time_for_job, valid_time_for_job def test_create_account_setup(session): """Test create account.""" - CreateAccountTask.create_accounts() - assert True + with freeze_time(valid_time_for_job): + CreateAccountTask.create_accounts() + assert True def test_create_pad_account(session): """Test create account.""" # Create a pending account first, then call the job - account = factory_create_pad_account(auth_account_id="1") - CreateAccountTask.create_accounts() - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) - assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value - assert cfs_account.bank_account_number - assert cfs_account.cfs_party - assert cfs_account.cfs_site - assert cfs_account.cfs_account - assert cfs_account.payment_instrument_number + with freeze_time(valid_time_for_job): + account = factory_create_pad_account(auth_account_id="1") + CreateAccountTask.create_accounts() + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) + assert cfs_account.status == CfsAccountStatus.PENDING_PAD_ACTIVATION.value + assert cfs_account.bank_account_number + assert cfs_account.cfs_party + assert cfs_account.cfs_site + assert cfs_account.cfs_account + assert cfs_account.payment_instrument_number def test_create_eft_account(session): """Test create account.""" # Create a pending account first, then call the job - account = factory_create_eft_account(auth_account_id="1") - CreateAccountTask.create_accounts() - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.EFT.value) - assert cfs_account.status == CfsAccountStatus.ACTIVE.value - assert cfs_account.payment_instrument_number is None + with freeze_time(valid_time_for_job): + account = factory_create_eft_account(auth_account_id="1") + CreateAccountTask.create_accounts() + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.EFT.value) + assert cfs_account.status == CfsAccountStatus.ACTIVE.value + assert cfs_account.payment_instrument_number is None def test_create_pad_account_user_error(session): """Test create account.""" # Create a pending account first, then call the job - account = factory_create_pad_account(auth_account_id="1") - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) - assert cfs_account.status == CfsAccountStatus.PENDING.value - mock_response = requests.models.Response() - mock_response.headers["CAS-Returned-Messages"] = "[Errors = [34] Bank Account Number is Invalid]" - mock_response.status_code = 404 + with freeze_time(valid_time_for_job): + account = factory_create_pad_account(auth_account_id="1") + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) + assert cfs_account.status == CfsAccountStatus.PENDING.value + mock_response = requests.models.Response() + mock_response.headers["CAS-Returned-Messages"] = "[Errors = [34] Bank Account Number is Invalid]" + mock_response.status_code = 404 - side_effect = HTTPError(response=mock_response) - with patch.object(mailer, "publish_mailer_events") as mock_mailer: - with patch("pay_api.services.CFSService.create_cfs_account", side_effect=side_effect): - CreateAccountTask.create_accounts() - mock_mailer.assert_called + side_effect = HTTPError(response=mock_response) + with patch.object(mailer, "publish_mailer_events") as mock_mailer: + with patch("pay_api.services.CFSService.create_cfs_account", side_effect=side_effect): + CreateAccountTask.create_accounts() + mock_mailer.assert_called - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_by_id(cfs_account.id) - assert cfs_account.status == CfsAccountStatus.INACTIVE.value + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_by_id(cfs_account.id) + assert cfs_account.status == CfsAccountStatus.INACTIVE.value def test_create_pad_account_system_error(session): """Test create account.""" # Create a pending account first, then call the job - account = factory_create_pad_account(auth_account_id="1") - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) - assert cfs_account.status == CfsAccountStatus.PENDING.value - mock_response = requests.models.Response() - mock_response.headers["CAS-Returned-Messages"] = "[CFS Down]" - mock_response.status_code = 404 + with freeze_time(valid_time_for_job): + account = factory_create_pad_account(auth_account_id="1") + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) + assert cfs_account.status == CfsAccountStatus.PENDING.value + mock_response = requests.models.Response() + mock_response.headers["CAS-Returned-Messages"] = "[CFS Down]" + mock_response.status_code = 404 - side_effect = HTTPError(response=mock_response) - with patch.object(mailer, "publish_mailer_events") as mock_mailer: - with patch("pay_api.services.CFSService.create_cfs_account", side_effect=side_effect): - CreateAccountTask.create_accounts() - mock_mailer.assert_not_called() + side_effect = HTTPError(response=mock_response) + with patch.object(mailer, "publish_mailer_events") as mock_mailer: + with patch("pay_api.services.CFSService.create_cfs_account", side_effect=side_effect): + CreateAccountTask.create_accounts() + mock_mailer.assert_not_called() - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_by_id(cfs_account.id) - assert cfs_account.status == CfsAccountStatus.PENDING.value + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_by_id(cfs_account.id) + assert cfs_account.status == CfsAccountStatus.PENDING.value def test_create_pad_account_no_confirmation_period(session): """Test create account.Arbitrary scenario when there is no confirmation period.""" # Create a pending account first, then call the job - account = factory_create_pad_account(auth_account_id="1", confirmation_period=0) - CreateAccountTask.create_accounts() - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) - assert cfs_account.status == CfsAccountStatus.ACTIVE.value - assert cfs_account.bank_account_number - assert cfs_account.cfs_party - assert cfs_account.cfs_site - assert cfs_account.cfs_account - assert cfs_account.payment_instrument_number + with freeze_time(valid_time_for_job): + account = factory_create_pad_account(auth_account_id="1", confirmation_period=0) + CreateAccountTask.create_accounts() + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) + assert cfs_account.status == CfsAccountStatus.ACTIVE.value + assert cfs_account.bank_account_number + assert cfs_account.cfs_party + assert cfs_account.cfs_site + assert cfs_account.cfs_account + assert cfs_account.payment_instrument_number def test_create_online_banking_account(session): """Test create account.""" # Create a pending account first, then call the job - account = factory_create_online_banking_account(auth_account_id="2") - CreateAccountTask.create_accounts() - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) - assert cfs_account.status == CfsAccountStatus.ACTIVE.value - assert not cfs_account.bank_account_number - assert cfs_account.cfs_party - assert cfs_account.cfs_site - assert cfs_account.cfs_account - assert cfs_account.payment_instrument_number is None + with freeze_time(valid_time_for_job): + account = factory_create_online_banking_account(auth_account_id="2") + CreateAccountTask.create_accounts() + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) + assert cfs_account.status == CfsAccountStatus.ACTIVE.value + assert not cfs_account.bank_account_number + assert cfs_account.cfs_party + assert cfs_account.cfs_site + assert cfs_account.cfs_account + assert cfs_account.payment_instrument_number is None def test_update_online_banking_account(session): """Test update account.""" # Create a pending account first, then call the job - account = factory_create_online_banking_account(auth_account_id="2") - CreateAccountTask.create_accounts() - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) + with freeze_time(valid_time_for_job): + account = factory_create_online_banking_account(auth_account_id="2") + CreateAccountTask.create_accounts() + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) - # Update account, which shouldn't change any details - OnlineBankingService().update_account(name="Test", cfs_account=cfs_account, payment_info=None) - updated_cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.ONLINE_BANKING.value) + # Update account, which shouldn't change any details + OnlineBankingService().update_account(name="Test", cfs_account=cfs_account, payment_info=None) + updated_cfs_account = CfsAccount.find_effective_by_payment_method( + account.id, PaymentMethod.ONLINE_BANKING.value + ) - assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value - assert cfs_account.id == updated_cfs_account.id + assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value + assert cfs_account.id == updated_cfs_account.id def test_update_pad_account(session): """Test update account.""" # Create a pending account first, then call the job - account = factory_create_pad_account(auth_account_id="2") - CreateAccountTask.create_accounts() - - account = PaymentAccount.find_by_id(account.id) - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) - - assert cfs_account.payment_instrument_number - - # Now update the account. - new_payment_details = { - "bankInstitutionNumber": "111", - "bankTransitNumber": "222", - "bankAccountNumber": "3333333333", - } - PadService().update_account(name="Test", cfs_account=cfs_account, payment_info=new_payment_details) - cfs_account = CfsAccount.find_by_id(cfs_account.id) - - # Run the job again - CreateAccountTask.create_accounts() - - updated_cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) - assert updated_cfs_account.id != cfs_account.id - assert updated_cfs_account.bank_account_number == new_payment_details.get("bankAccountNumber") - assert updated_cfs_account.bank_branch_number == new_payment_details.get("bankTransitNumber") - assert updated_cfs_account.bank_number == new_payment_details.get("bankInstitutionNumber") - - assert cfs_account.status == CfsAccountStatus.INACTIVE.value - assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value - assert updated_cfs_account.payment_instrument_number + with freeze_time(valid_time_for_job): + account = factory_create_pad_account(auth_account_id="2") + CreateAccountTask.create_accounts() + + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) + + assert cfs_account.payment_instrument_number + + # Now update the account. + new_payment_details = { + "bankInstitutionNumber": "111", + "bankTransitNumber": "222", + "bankAccountNumber": "3333333333", + } + PadService().update_account(name="Test", cfs_account=cfs_account, payment_info=new_payment_details) + cfs_account = CfsAccount.find_by_id(cfs_account.id) + + # Run the job again + CreateAccountTask.create_accounts() + + updated_cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.PAD.value) + assert updated_cfs_account.id != cfs_account.id + assert updated_cfs_account.bank_account_number == new_payment_details.get("bankAccountNumber") + assert updated_cfs_account.bank_branch_number == new_payment_details.get("bankTransitNumber") + assert updated_cfs_account.bank_number == new_payment_details.get("bankInstitutionNumber") + + assert cfs_account.status == CfsAccountStatus.INACTIVE.value + assert updated_cfs_account.status == CfsAccountStatus.ACTIVE.value + assert updated_cfs_account.payment_instrument_number + + +def test_invalid_time_for_job(session): + """Try creating EFT account outside of CFS hopurs.""" + with freeze_time(invalid_time_for_job): + account = factory_create_eft_account(auth_account_id="1") + CreateAccountTask.create_accounts() + account = PaymentAccount.find_by_id(account.id) + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.EFT.value) + assert cfs_account.status == CfsAccountStatus.PENDING.value diff --git a/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py b/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py index daa26d418..fbef7a99d 100644 --- a/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py +++ b/jobs/payment-jobs/tests/jobs/test_cfs_create_routing_slip_account_task.py @@ -17,21 +17,24 @@ Test-Suite to ensure that the CreateAccountTask for routing slip is working as expected. """ +from freezegun import freeze_time from pay_api.models import CfsAccount from pay_api.utils.enums import CfsAccountStatus, PaymentMethod from tasks.cfs_create_account_task import CreateAccountTask from .factory import factory_routing_slip_account +from .utils import valid_time_for_job def test_create_rs_account(session): """Test create account.""" # Create a pending account first, then call the job - account = factory_routing_slip_account() - CreateAccountTask.create_accounts() - cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.INTERNAL.value) - assert cfs_account.status == CfsAccountStatus.ACTIVE.value - assert cfs_account.cfs_party - assert cfs_account.cfs_site - assert cfs_account.cfs_account + with freeze_time(valid_time_for_job): + account = factory_routing_slip_account() + CreateAccountTask.create_accounts() + cfs_account = CfsAccount.find_effective_by_payment_method(account.id, PaymentMethod.INTERNAL.value) + assert cfs_account.status == CfsAccountStatus.ACTIVE.value + assert cfs_account.cfs_party + assert cfs_account.cfs_site + assert cfs_account.cfs_account diff --git a/jobs/payment-jobs/tests/jobs/utils.py b/jobs/payment-jobs/tests/jobs/utils.py new file mode 100644 index 000000000..e38c9b594 --- /dev/null +++ b/jobs/payment-jobs/tests/jobs/utils.py @@ -0,0 +1,9 @@ +"""Utility functions for testing.""" + +from datetime import datetime + +import pytz + +# Fit between CAS hours of operation. +valid_time_for_job = datetime.now(pytz.timezone("America/Vancouver")).replace(hour=12) +invalid_time_for_job = datetime.now(pytz.timezone("America/Vancouver")).replace(hour=1)