"""Functional tests for payment search endpoint.""" from unittest.mock import MagicMock, patch from payment.constants.constants import PAYMENT_SEARCH_STATUSES, PAYMENT_SEARCH_TYPES from payment.constants.error import ERROR_PAYMENTS_SEARCH_REQUIRED_FILTERS from tests.utils.factories import PaymentSearchItemFactory URL = '/payment-search/search' # Headers that trigger the access-rules check (requestor service must match # graphql-) for a profile-based request. ACCOUNT360_HEADERS = { 'Orchard-Profile-Type': 'Account360Profile', 'Orchard-Profile-Id': '123456', 'Orchard-Roles': 'account360', 'Orchard-Requestor-Service': 'graphql-abacus', } ABACUS_HEADERS = { 'Orchard-Profile-Type': 'AbacusProfile', 'Orchard-Profile-Id': '123456', 'Orchard-Roles': 'administrator', 'Orchard-Requestor-Service': 'graphql-abacus', } UNAUTHORIZED_PROFILE_HEADERS = { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '123456', 'Orchard-Roles': 'content_reviewer', 'Orchard-Requestor-Service': 'graphql-abacus', } def _mock_client(items): client = MagicMock() client.search.return_value = items return client @patch('payment.logic.payment_search.get_client') def test_search_payments_by_account_success(mock_get_client, fixture_client) -> None: """POST /payment-search/search returns 200 with items filtered by account.""" items = PaymentSearchItemFactory.create_batch(2) mock_get_client.return_value = _mock_client(items) res = fixture_client.post(URL, json={'filters': {'account': 'ACC-001'}}) assert res.status_code == 200 assert res.json['total_count'] == 2 assert len(res.json['items']) == 2 @patch('payment.logic.payment_search.get_client') def test_search_payments_by_contract_success(mock_get_client, fixture_client) -> None: """POST /payment-search/search returns 200 with items filtered by contract.""" items = PaymentSearchItemFactory.create_batch(3) mock_get_client.return_value = _mock_client(items) res = fixture_client.post(URL, json={'filters': {'contract': 'CNT-999'}}) assert res.status_code == 200 assert res.json['total_count'] == 3 assert len(res.json['items']) == 3 @patch('payment.logic.payment_search.get_client') def test_search_payments_by_payment_types_success( mock_get_client, fixture_client ) -> None: """POST /payment-search/search returns 200 filtered by payment_types list.""" items = PaymentSearchItemFactory.create_batch(2) mock_get_client.return_value = _mock_client(items) res = fixture_client.post( URL, json={ 'filters': { 'payment_types': [ PAYMENT_SEARCH_TYPES.BALANCE_PAYMENT, PAYMENT_SEARCH_TYPES.ADVANCE_PAYMENT, ] } }, ) assert res.status_code == 200 assert res.json['total_count'] == 2 @patch('payment.logic.payment_search.get_client') def test_search_payments_by_payment_statuses_success( mock_get_client, fixture_client ) -> None: """POST /payment-search/search returns 200 filtered by payment_statuses list.""" items = PaymentSearchItemFactory.create_batch(1) mock_get_client.return_value = _mock_client(items) res = fixture_client.post( URL, json={ 'filters': { 'payment_statuses': [ PAYMENT_SEARCH_STATUSES.SUCCESSFUL, PAYMENT_SEARCH_STATUSES.FAILURE, ] } }, ) assert res.status_code == 200 assert res.json['total_count'] == 1 @patch('payment.logic.payment_search.get_client') def test_search_payments_by_payment_release_date_range_success( mock_get_client, fixture_client ) -> None: """POST /payment-search/search returns 200 filtered by payment release date range.""" items = PaymentSearchItemFactory.create_batch(2) mock_get_client.return_value = _mock_client(items) res = fixture_client.post( URL, json={ 'filters': { 'payment_release_date_from': '2025-01-01', 'payment_release_date_to': '2025-12-31', } }, ) assert res.status_code == 200 assert res.json['total_count'] == 2 @patch('payment.logic.payment_search.get_client') def test_search_payments_by_payment_created_date_range_success( mock_get_client, fixture_client ) -> None: """POST /payment-search/search returns 200 filtered by payment created date range.""" items = PaymentSearchItemFactory.create_batch(2) mock_get_client.return_value = _mock_client(items) res = fixture_client.post( URL, json={ 'filters': { 'payment_created_date_from': '2025-03-01', 'payment_created_date_to': '2025-03-31', } }, ) assert res.status_code == 200 assert res.json['total_count'] == 2 @patch('payment.logic.payment_search.get_client') def test_search_payments_multiple_filters_success( mock_get_client, fixture_client ) -> None: """POST /payment-search/search returns 200 with multiple combined filters.""" items = PaymentSearchItemFactory.create_batch(1) mock_get_client.return_value = _mock_client(items) res = fixture_client.post( URL, json={ 'filters': { 'account': 'ACC-001', 'payment_types': [PAYMENT_SEARCH_TYPES.BALANCE_PAYMENT], 'payment_statuses': [PAYMENT_SEARCH_STATUSES.SUCCESSFUL], 'payment_release_date_from': '2025-01-01', 'payment_release_date_to': '2025-12-31', } }, ) assert res.status_code == 200 assert res.json['total_count'] == 1 @patch('payment.logic.payment_search.get_client') def test_search_payments_empty_results(mock_get_client, fixture_client) -> None: """POST /payment-search/search returns 200 with empty items list.""" mock_get_client.return_value = _mock_client([]) res = fixture_client.post(URL, json={'filters': {'account': 'ACC-UNKNOWN'}}) assert res.status_code == 200 assert res.json['total_count'] == 0 assert res.json['items'] == [] @patch('payment.logic.payment_search.get_client') def test_search_payments_authorized_for_account360_profile( mock_get_client, fixture_client ) -> None: """Account360Profile users are authorized to search payments (TAP-3437).""" items = PaymentSearchItemFactory.create_batch(1) mock_get_client.return_value = _mock_client(items) res = fixture_client.post( URL, json={'filters': {'account': 'ACC-001'}}, headers=ACCOUNT360_HEADERS ) assert res.status_code == 200 assert res.json['total_count'] == 1 @patch('payment.logic.payment_search.get_client') def test_search_payments_authorized_for_abacus_profile( mock_get_client, fixture_client ) -> None: """AbacusProfile administrators remain authorized to search payments.""" items = PaymentSearchItemFactory.create_batch(1) mock_get_client.return_value = _mock_client(items) res = fixture_client.post( URL, json={'filters': {'account': 'ACC-001'}}, headers=ABACUS_HEADERS ) assert res.status_code == 200 assert res.json['total_count'] == 1 @patch('payment.logic.payment_search.get_client') def test_search_payments_unauthorized_for_other_profile( mock_get_client, fixture_client ) -> None: """Profiles without a granting rule are not authorized to search payments.""" mock_get_client.return_value = _mock_client([]) res = fixture_client.post( URL, json={'filters': {'account': 'ACC-001'}}, headers=UNAUTHORIZED_PROFILE_HEADERS, ) assert res.status_code == 403 assert res.json == {'code': 'authorization_error', 'message': 'Unauthorized'} def test_search_payments_no_filters_returns_400(fixture_client) -> None: """POST /payment-search/search with no filters returns 400.""" res = fixture_client.post(URL, json={}) assert res.status_code == 400 assert res.json.get('message') == {'json': {'filters': ['Must be specified.']}} def test_search_payments_invalid_payment_type_returns_400(fixture_client) -> None: """POST /payment-search/search with invalid payment_type returns 400.""" res = fixture_client.post( URL, json={'filters': {'payment_types': ['invalid_type']}} ) assert res.status_code == 400 def test_search_payments_invalid_payment_status_returns_400(fixture_client) -> None: """POST /payment-search/search with invalid payment_status returns 400.""" res = fixture_client.post( URL, json={'filters': {'payment_statuses': ['invalid_status']}} ) assert res.status_code == 400 def test_search_payments_payment_release_date_range_invalid_returns_400( fixture_client, ) -> None: """POST /payment-search/search with payment_release_date_from > payment_release_date_to returns 400.""" res = fixture_client.post( URL, json={ 'filters': { 'payment_release_date_from': '2025-12-31', 'payment_release_date_to': '2025-01-01', } }, ) assert res.status_code == 400 def test_search_payments_payment_created_date_range_invalid_returns_400( fixture_client, ) -> None: """POST /payment-search/search with payment_created_date_from > payment_created_date_to returns 400.""" res = fixture_client.post( URL, json={ 'filters': { 'payment_created_date_from': '2025-12-31', 'payment_created_date_to': '2025-01-01', } }, ) assert res.status_code == 400