"""Accounting run logic tests.""" from unittest.mock import MagicMock, patch import pytest from royalties.constants import error from royalties.constants.constants import ( ACCOUNTING_PERIOD_STATUSES, ACCOUNTING_RUN_STATUSES as STATUSES, ) from royalties.logic import accounting_run as logic from royalties.tests.utils.factories import ( AccountingPeriodFactory, AccountingRunFactory, ) @pytest.fixture def mock_models(): """Mock the model layer.""" with patch('royalties.logic.accounting_run.models') as models: yield models @pytest.fixture def mock_change_handlers(): """Set up mock change handlers.""" change_handlers = 'royalties.logic.accounting_run.change_handlers' mock_handlers = { STATUSES.WAITING_TO_RUN: MagicMock(), STATUSES.INVALID: MagicMock(), STATUSES.COMPLETE: MagicMock(), STATUSES.COMMITTING: MagicMock(), } with patch.dict(change_handlers, mock_handlers): yield mock_handlers @patch('royalties.logic.accounting_run.create_presigned_url') @patch('royalties.logic.accounting_run.get_s3_client') def test_run_summary_items_csv(mock_client, mock_url, mock_models): """Test getting a presigned URL to download run summary items csv.""" accounting_run = AccountingRunFactory.build( summary_export_url='s3://sales-files/1-march-2020/output/run_summary.csv' ) mock_models.AccountingRun.get_by_id.return_value = accounting_run mock_client.return_value = 'client' logic.get_run_summary_items_csv(accounting_run.accounting_run_id) mock_client.assert_called_once() mock_url.assert_called_once_with( 'client', 'sales-files', '1-march-2020/output/run_summary.csv' ) def test_run_summary_items_csv_failure(mock_models): """Test get run summary items csv on empty summary export url.""" accounting_run = AccountingRunFactory.build(summary_export_url=None) mock_models.AccountingRun.get_by_id.return_value = accounting_run result = logic.get_run_summary_items_csv(accounting_run.accounting_run_id) assert result.status == 400 assert result.errors['message'] == error.ERROR_NO_SUMMARY_EXPORT_URL.format( accounting_run_id=accounting_run.accounting_run_id ) def test_accounting_run_create_action(mock_models): """Create action succeeds if the run status is 'No Action Taken'.""" accounting_run = AccountingRunFactory.build() resp = logic.update_accounting_run( accounting_run, run_status=STATUSES.WAITING_TO_RUN ) assert resp.status == 200 assert resp.message == logic.detail_schema.dump([accounting_run], many=True) assert accounting_run.run_status == STATUSES.WAITING_TO_RUN mock_models.AccountingRun.commit_changes.assert_called_once() def test_accounting_run_skip_action(mock_models): """Skip action succeeds if the run status is 'No Action Taken'.""" accounting_run = AccountingRunFactory.build() resp = logic.update_accounting_run(accounting_run, run_status=STATUSES.SKIPPED) assert resp.status == 200 assert resp.message == logic.detail_schema.dump([accounting_run], many=True) assert accounting_run.run_status == STATUSES.SKIPPED mock_models.AccountingRun.commit_changes.assert_called_once() def test_update_accounting_run_to_running(mock_models): """Mark accounting run as running.""" accounting_run = AccountingRunFactory.build(run_status=STATUSES.WAITING_TO_RUN) resp = logic.update_accounting_run(accounting_run, run_status=STATUSES.RUNNING) assert resp.status == 200 assert resp.message == logic.detail_schema.dump([accounting_run], many=True) assert accounting_run.run_status == STATUSES.RUNNING mock_models.AccountingRun.commit_changes.assert_called_once() def test_update_accounting_run_to_complete(mock_models): """Mark accounting run as complete.""" accounting_run = AccountingRunFactory.build(run_status=STATUSES.RUNNING) assert accounting_run.end_date is None resp = logic.update_accounting_run(accounting_run, run_status=STATUSES.COMPLETE) assert resp.status == 200 assert resp.message == logic.detail_schema.dump([accounting_run], many=True) assert accounting_run.run_status == STATUSES.COMPLETE assert accounting_run.end_date is not None mock_models.AccountingRun.commit_changes.assert_called_once() def test_update_accounting_run_fails_with_invalid_status_change(mock_models): """Create or skip action is invalid if status is not 'No Action Taken'.""" accounting_run = AccountingRunFactory.build() resp = logic.update_accounting_run(accounting_run, run_status=STATUSES.COMPLETE) assert resp.status == 400 assert resp.errors['message'] == error.ERROR_INVALID_STATUS_CHANGE.format( object_type='Accounting run', current_status=STATUSES.NO_ACTION_TAKEN, new_status=STATUSES.COMPLETE, ) mock_models.AccountingRun.commit_changes.assert_not_called() def test_update_accounting_run_fails_with_closed_period(mock_models): """Only runs attached to the active accounting period can be updated.""" closed_period = AccountingPeriodFactory.build( accounting_period_status=ACCOUNTING_PERIOD_STATUSES.CLOSED, closed_date='2019-01-01 13:00:00.00', ) run = AccountingRunFactory.build(accounting_period=closed_period) resp = logic.update_accounting_run(run, run_status=STATUSES.COMPLETE) assert resp.errors['message'] == error.ERROR_CANNOT_UPDATE mock_models.AccountingRun.commit_changes.assert_not_called() def test_invalidate_accounting_run(mock_models): """Create new run from invalid run.""" run = AccountingRunFactory.build( run_status=STATUSES.COMPLETE, ) logic.update_accounting_run(run, run_status=STATUSES.INVALID) mock_models.AccountingRun.commit_changes.assert_called_once() def test_update_accounting_run_allowed_transitions(mock_models, mock_change_handlers): """Test accounting run status transitions.""" no_action_run = AccountingRunFactory.build(run_status=STATUSES.NO_ACTION_TAKEN) waiting_run = AccountingRunFactory.build(run_status=STATUSES.WAITING_TO_RUN) running_run = AccountingRunFactory.build(run_status=STATUSES.RUNNING) skipped_run = AccountingRunFactory.build(run_status=STATUSES.SKIPPED) completed_run = AccountingRunFactory.build(run_status=STATUSES.COMPLETE) completed_run_2 = AccountingRunFactory.build(run_status=STATUSES.COMPLETE) committing_run = AccountingRunFactory.build(run_status=STATUSES.COMMITTING) committing_run_2 = AccountingRunFactory.build(run_status=STATUSES.COMMITTING) errored_run = AccountingRunFactory.build(run_status=STATUSES.ERROR) errored_run_2 = AccountingRunFactory.build(run_status=STATUSES.ERROR) errored_run_3 = AccountingRunFactory.build(run_status=STATUSES.ERROR) errored_run_4 = AccountingRunFactory.build(run_status=STATUSES.ERROR) assert logic.update_accounting_run(no_action_run, STATUSES.WAITING_TO_RUN) assert logic.update_accounting_run(skipped_run, STATUSES.NO_ACTION_TAKEN) assert logic.update_accounting_run(waiting_run, STATUSES.RUNNING) assert logic.update_accounting_run(running_run, STATUSES.COMPLETE) assert logic.update_accounting_run(completed_run, STATUSES.INVALID) assert logic.update_accounting_run(completed_run_2, STATUSES.COMMITTING) assert logic.update_accounting_run(committing_run, STATUSES.COMMITTED) assert logic.update_accounting_run(committing_run_2, STATUSES.ERROR) assert logic.update_accounting_run(errored_run, STATUSES.COMPLETE) assert logic.update_accounting_run(errored_run_2, STATUSES.COMMITTING) assert logic.update_accounting_run(errored_run_3, STATUSES.RUNNING) assert logic.update_accounting_run(errored_run_4, STATUSES.COMMITTED)