"""Tests for submit_dp_payments logic.""" from datetime import datetime from types import SimpleNamespace from unittest.mock import patch import pytest from collaborator.constants import error from collaborator.constants.abacus_statement_period import PAYONEER_PAYMENT_DESCRIPTION from collaborator.logic import abacus_statement_period as logic from collaborator.models.rds.dp_payment import DpPayment, PayoneerPaymentStatus from collaborator.utils.error import OwsError # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- APPROVED_DATE = datetime(2026, 9, 10, 12, 0, 0) TEST_USER = SimpleNamespace(id="test-user") def _make_payment(**overrides): """Return a DpPayment instance with sensible defaults.""" defaults = { "dp_payment_id": 1, "abacus_statement_period_id": 1, "abacus_statement_period_name": "Jan 2026", "collaborator_id": 1, "collaborator_name": "John Doe", "amount": 1500.00, "currency": "USD", "payee_id": 123, "payoneer_program_id": "100000", "payoneer_program_name": "Program 1", "payoneer_client_reference_id": "a1b2c3d4-e5f6-4abc-8def-012345678901", "account_id": 24601, "account_name": "Acme Music", "created_date": datetime(2026, 9, 8, 7, 6, 5), "collaborator_transaction_id": None, "approved_date": APPROVED_DATE, "agreement_type": "Orch US", "payoneer_payment_id": "split:1:1", "payoneer_payment_status": None, } defaults.update(overrides) return DpPayment(**defaults) # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- @patch("collaborator.logic.abacus_statement_period.ows_payee") @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_success(mock_persister, mock_ows_payee): """Happy path: one approved payment → one create_mass_payouts call.""" dp = _make_payment() mock_persister.get_by_filters.return_value = [(dp, 1500.0)] mock_ows_payee.create_mass_payouts.return_value = {"batch_id": "b1"} result = logic.submit_dp_payments(abacus_statement_period_id=1, user=TEST_USER) assert result["submitted_count"] == 1 mock_ows_payee.create_mass_payouts.assert_called_once_with( 100000, [ { "client_reference_id": "split:1:1", "payee_id": "a1b2c3d4-e5f6-4abc-8def-012345678901", "amount": 1500.0, "currency": "USD", "description": PAYONEER_PAYMENT_DESCRIPTION, } ], ) mock_persister.update_payoneer_status_bulk.assert_called_once_with( [1], PayoneerPaymentStatus.INIT, event_type=None, reason=None, user=TEST_USER ) @patch("collaborator.logic.abacus_statement_period.ows_payee") @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_multiple_programs(mock_persister, mock_ows_payee): """Payments across two programs → two separate create_mass_payouts calls.""" dp1 = _make_payment( dp_payment_id=1, collaborator_id=1, collaborator_name="John Doe", payee_id=123, amount=1500.00, payoneer_program_id="100000", payoneer_payment_id="split:1:1", ) dp2 = _make_payment( dp_payment_id=2, collaborator_id=2, collaborator_name="Jane Smith", payee_id=456, amount=2750.50, payoneer_program_id="200000", payoneer_payment_id="split:1:2", ) mock_persister.get_by_filters.return_value = [ (dp1, 4250.5), (dp2, 4250.5), ] mock_ows_payee.create_mass_payouts.return_value = {} result = logic.submit_dp_payments(abacus_statement_period_id=1, user=TEST_USER) assert result["submitted_count"] == 2 assert mock_ows_payee.create_mass_payouts.call_count == 2 assert mock_persister.update_payoneer_status_bulk.call_count == 2 @patch("collaborator.logic.abacus_statement_period.ows_payee") @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_groups_same_program(mock_persister, mock_ows_payee): """Two approved payments with the same program_id → single create_mass_payouts call.""" dp1 = _make_payment( dp_payment_id=1, collaborator_id=1, collaborator_name="John Doe", payee_id=123, payoneer_client_reference_id="a1b2c3d4-e5f6-4abc-8def-000000000001", amount=1500.00, payoneer_program_id="100000", payoneer_payment_id="split:1:1", ) dp2 = _make_payment( dp_payment_id=2, collaborator_id=2, collaborator_name="Jane Smith", payee_id=456, payoneer_client_reference_id="a1b2c3d4-e5f6-4abc-8def-000000000002", amount=2750.50, payoneer_program_id="100000", payoneer_payment_id="split:1:2", ) mock_persister.get_by_filters.return_value = [ (dp1, 4250.5), (dp2, 4250.5), ] mock_ows_payee.create_mass_payouts.return_value = {} result = logic.submit_dp_payments(abacus_statement_period_id=1, user=TEST_USER) assert result["submitted_count"] == 2 mock_ows_payee.create_mass_payouts.assert_called_once_with( 100000, [ { "client_reference_id": "split:1:1", "payee_id": "a1b2c3d4-e5f6-4abc-8def-000000000001", "amount": 1500.0, "currency": "USD", "description": PAYONEER_PAYMENT_DESCRIPTION, }, { "client_reference_id": "split:1:2", "payee_id": "a1b2c3d4-e5f6-4abc-8def-000000000002", "amount": 2750.5, "currency": "USD", "description": PAYONEER_PAYMENT_DESCRIPTION, }, ], ) mock_persister.update_payoneer_status_bulk.assert_called_once_with( [1, 2], PayoneerPaymentStatus.INIT, event_type=None, reason=None, user=TEST_USER ) @patch("collaborator.logic.abacus_statement_period.ows_payee") @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_raises_422_when_no_approved(mock_persister, mock_ows_payee): """No approved payments → OwsError with 422 status.""" unapproved = _make_payment(approved_date=None) mock_persister.get_by_filters.return_value = [(unapproved, 1500.0)] with pytest.raises(OwsError) as exc_info: logic.submit_dp_payments(abacus_statement_period_id=1, user=TEST_USER) assert exc_info.value.status == 422 assert exc_info.value.code == error.ERROR_CODE_DP_PAYMENT_NOT_APPROVED assert exc_info.value.message == error.ERROR_MESSAGE_DP_PAYMENT_NOT_APPROVED mock_ows_payee.create_mass_payouts.assert_not_called() @patch("collaborator.logic.abacus_statement_period.ows_payee") @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_raises_422_when_empty(mock_persister, mock_ows_payee): """Empty payments list → OwsError with 422 status.""" mock_persister.get_by_filters.return_value = [] with pytest.raises(OwsError) as exc_info: logic.submit_dp_payments(abacus_statement_period_id=1, user=TEST_USER) assert exc_info.value.status == 422 assert exc_info.value.code == error.ERROR_CODE_DP_PAYMENT_NOT_APPROVED mock_ows_payee.create_mass_payouts.assert_not_called() @patch("collaborator.logic.abacus_statement_period.ows_payee") @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_skips_unapproved(mock_persister, mock_ows_payee): """Mix of approved and unapproved: only approved ones are submitted.""" approved = _make_payment( dp_payment_id=1, collaborator_id=1, collaborator_name="John Doe", payee_id=123, amount=1500.00, payoneer_program_id="100000", payoneer_payment_id="split:1:1", approved_date=APPROVED_DATE, ) unapproved = _make_payment( dp_payment_id=2, collaborator_id=2, collaborator_name="Jane Smith", payee_id=456, amount=2750.50, payoneer_program_id="100000", payoneer_payment_id="split:1:2", approved_date=None, ) mock_persister.get_by_filters.return_value = [ (approved, 4250.5), (unapproved, 4250.5), ] mock_ows_payee.create_mass_payouts.return_value = {} result = logic.submit_dp_payments(abacus_statement_period_id=1, user=TEST_USER) assert result["submitted_count"] == 1 mock_ows_payee.create_mass_payouts.assert_called_once_with( 100000, [ { "client_reference_id": "split:1:1", "payee_id": "a1b2c3d4-e5f6-4abc-8def-012345678901", "amount": 1500.0, "currency": "USD", "description": PAYONEER_PAYMENT_DESCRIPTION, } ], ) @patch("collaborator.logic.abacus_statement_period.ows_payee") @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_skips_already_submitted(mock_persister, mock_ows_payee): """Payments already submitted (payoneer_payment_status set) are not re-submitted.""" already_submitted = _make_payment( dp_payment_id=1, payoneer_payment_status=PayoneerPaymentStatus.INIT, ) not_yet_submitted = _make_payment( dp_payment_id=2, collaborator_id=2, payee_id=456, amount=2750.50, payoneer_payment_id="split:1:2", payoneer_payment_status=None, ) mock_persister.get_by_filters.return_value = [ (already_submitted, 4250.5), (not_yet_submitted, 4250.5), ] mock_ows_payee.create_mass_payouts.return_value = {} result = logic.submit_dp_payments(abacus_statement_period_id=1, user=TEST_USER) assert result["submitted_count"] == 1 mock_ows_payee.create_mass_payouts.assert_called_once() mock_persister.update_payoneer_status_bulk.assert_called_once_with( [2], PayoneerPaymentStatus.INIT, event_type=None, reason=None, user=TEST_USER ) @patch("collaborator.logic.abacus_statement_period.DpPaymentPersister") def test_submit_dp_payments_queries_correct_period(mock_persister): """The persister is called with the correct abacus_statement_period_id.""" mock_persister.get_by_filters.return_value = [] with pytest.raises(OwsError): logic.submit_dp_payments(abacus_statement_period_id=42, user=TEST_USER) mock_persister.get_by_filters.assert_called_once_with(abacus_statement_period_id=42)