"""Test for the statement period logic.""" from unittest.mock import Mock import pytest from collaborator.constants import error from collaborator.logic import statement_period from collaborator.utils.error import OwsError def _make_closed_period(vendor_id): return dict( id=vendor_id, name="name", vendor_id=vendor_id, created_date="created_date", updated_date="updated_date", status="status", ) def test_close_statement_period(mocker, mock_account): """Test successfully closing a statement period.""" period_name = "Period Name" mock_txns_for_open_period = mocker.patch.object( statement_period.TransactionPersister, "get_transactions_for_open_period" ) mock_close_period = mocker.patch.object( statement_period.StatementPeriodPersister, "close_statement_period" ) mock_get_collabs_with_activity = mocker.patch.object( statement_period.CollaboratorPersister, "get_with_statement_activity", ) mock_trigger_notifications = mocker.patch.object( statement_period.ows_notifications, "trigger_closed_period_notifications" ) mock_get_collabs_with_activity.return_value = [1234] mock_txns_for_open_period.return_value = ([], 1) mock_close_period.return_value = ( _make_closed_period(419), _make_closed_period(420), ) response = statement_period.close_statement_period( int(mock_account.id), period_name ) mock_close_period.assert_called_with(int(mock_account.id), period_name) mock_trigger_notifications.assert_called_with( [ { "active_collaborator_ids": [1234], "statement_period": _make_closed_period(419), } ] ) assert response["id"] == 420 def test_close_empty_statement_period_raises(mocker, mock_account): """Test closing an statement period raises.""" period_name = "Period Name" mock_txns_for_open_period = mocker.patch.object( statement_period.TransactionPersister, "get_transactions_for_open_period" ) mock_close_period = mocker.patch.object( statement_period.StatementPeriodPersister, "close_statement_period" ) mock_txns_for_open_period.return_value = ([], 0) with pytest.raises(OwsError) as err: statement_period.close_statement_period(int(mock_account.id), period_name) mock_close_period.assert_not_called() assert err.value.code == (error.ERROR_CODE_CANNOT_CLOSE_EMPTY_PERIOD) assert err.value.message == (error.ERROR_MESSAGE_CANNOT_CLOSE_EMPTY_PERIOD) def test_close_statement_period_abacus(mocker, mock_account): """Test successfully closing a statement period.""" period_name = "Period Name" mock_txns_for_open_period = mocker.patch.object( statement_period.TransactionPersister, "get_transactions_for_open_period" ) mock_close_period = mocker.patch.object( statement_period.StatementPeriodPersister, "close_statement_period" ) mock_get_collabs_with_activity = mocker.patch.object( statement_period.CollaboratorPersister, "get_with_statement_activity", ) mocker.patch.object( statement_period.ows_notifications, "trigger_closed_period_notifications" ) mock_txns_for_open_period.return_value = ([], 1) mock_close_period.return_value = ( {}, { "id": 420, "name": "SP 420", }, ) mock_get_collabs_with_activity.return_value = [1234] response = statement_period.close_statement_period( int(mock_account.id), period_name ) mock_close_period.assert_called_with(int(mock_account.id), period_name) assert response == { "id": 420, "name": "SP 420", } def test_check_vendor_currency_mismatch(mocker, mock_account): """Test for check_vendor_currency_mismatch.""" mock_get_abacus_account = mocker.patch.object( statement_period.ows_abacus_account, "get_abacus_account_metadata" ) mock_get_abacus_account.return_value = { "account_id": mock_account.id, "currency_code": "USD", } result = statement_period.check_vendor_currency_mismatch(mock_account.id, None) assert result == "USD" def test_statement_period_vendor_totals_dataloader(mocker): """Test statement period vendor totals dataloader endpoint logic.""" statement_period_ids = [1, 2, 3, 4, 5, 6] authorized_resources = [] # success result_1 = Mock() result_1.statement_period_id = 1 result_1.vendor_id = 101 result_1.currencies_count = 1 result_1.currency = "USD" result_1.revenues_total = 100 result_1.expenses_total = 500 result_1.payments_total = 0 # success - no transactions result_2 = Mock() result_2.statement_period_id = 2 result_2.vendor_id = 102 result_2.currencies_count = 0 result_2.currency = None result_2.revenues_total = 0 result_2.expenses_total = 0 result_2.payments_total = 0 # error - transactions in multiple currencies result_3 = Mock() result_3.statement_period_id = 3 result_3.vendor_id = 102 result_3.currencies_count = 2 result_3.currency = "USD" result_3.revenues_total = 5000 result_3.expenses_total = 0 result_3.payments_total = 0 # error - transactions currency doesn't match vendor currency result_4 = Mock() result_4.statement_period_id = 4 result_4.vendor_id = 101 result_4.currencies_count = 1 result_4.currency = "GBP" result_4.revenues_total = 100 result_4.expenses_total = 0 result_4.payments_total = 100 # null - unauthorised vendor result_5 = Mock() result_5.statement_period_id = 5 result_5.vendor_id = 103 result_5.currencies_count = 1 result_5.currency = "USD" result_5.revenues_total = 100 result_5.expenses_total = 500 result_5.payments_total = 0 mock_get_vendor_totals_for_statement_periods = mocker.patch.object( statement_period.StatementPeriodPersister, "get_vendor_totals_for_statement_periods", ) mock_get_vendor_totals_for_statement_periods.return_value = [ result_1, result_2, result_3, result_4, result_5, ] mock_check_vendors_authorization = mocker.patch.object( statement_period, "check_vendors_authorization" ) mock_check_vendors_authorization.return_value = [101, 102] mock_check_vendor_currency_mismatch = mocker.patch.object( statement_period, "check_vendor_currency_mismatch" ) mock_check_vendor_currency_mismatch.return_value = "USD" result = statement_period.statement_period_vendor_totals_dataloader( statement_period_ids, authorized_resources ) assert result == [ { "data": { "statement_period_id": 1, "revenues_total": {"amount": 100.0, "currency": "USD"}, "expenses_total": {"amount": 500.0, "currency": "USD"}, "payments_total": {"amount": 0.0, "currency": "USD"}, } }, { "data": { "statement_period_id": 2, "revenues_total": {"amount": 0.0, "currency": "USD"}, "expenses_total": {"amount": 0.0, "currency": "USD"}, "payments_total": {"amount": 0.0, "currency": "USD"}, } }, { "error": { "code": "period_currency_mismatch", "message": ( "Got a currency mismatch when calculating statement period totals." ), } }, { "error": { "code": "period_currency_mismatch", "message": ( "Got a currency mismatch when calculating statement period totals." ), } }, {"data": None}, {"data": None}, ] def test_bulk_close_statement_periods(mocker, mock_account): """Test bulk closing statement periods.""" period_name = "May 2025" vendor_ids = [10001, 10002, 10003] new_abacus_statement_period_id = 123 mock_get_vendors_for_feature = mocker.patch.object( statement_period.ows_account, "get_vendors_for_feature" ) mock_get_vendors_for_feature.return_value = vendor_ids mock_bulk_close_statement_periods = mocker.patch.object( statement_period.StatementPeriodPersister, "bulk_close_statement_periods", ) mock_bulk_close_statement_periods.return_value = { vendor_id: _make_closed_period(vendor_id) for vendor_id in vendor_ids } mock_bulk_get_with_statement_activity = mocker.patch.object( statement_period.CollaboratorPersister, "bulk_get_with_statement_activity", ) mock_bulk_get_with_statement_activity.return_value = { vendor_id: [vendor_id] for vendor_id in vendor_ids } mock_trigger_notifications = mocker.patch.object( statement_period.ows_notifications, "trigger_closed_period_notifications" ) statement_period.bulk_close_statement_periods( period_name, new_abacus_statement_period_id ) mock_get_vendors_for_feature.assert_called_with( statement_period.features.DIRECT_PAYMENTS_FEATURE_CONTROL ) mock_bulk_close_statement_periods.assert_called_with( period_name, vendor_ids, new_abacus_statement_period_id ) mock_trigger_notifications.assert_called_with( [ { "active_collaborator_ids": [10001], "statement_period": _make_closed_period(10001), }, { "active_collaborator_ids": [10002], "statement_period": _make_closed_period(10002), }, { "active_collaborator_ids": [10003], "statement_period": _make_closed_period(10003), }, ] )