diff --git a/tests/orchestration/test_tenant_data_sync_orchestrator.py b/tests/orchestration/test_tenant_data_sync_orchestrator.py index 82aeac847..798b82d74 100644 --- a/tests/orchestration/test_tenant_data_sync_orchestrator.py +++ b/tests/orchestration/test_tenant_data_sync_orchestrator.py @@ -3,13 +3,15 @@ from pathlib import Path from unittest.mock import MagicMock, patch import pytest +import requests from hubspot.crm.associations.v4 import AssociationSpec # type: ignore[reportMissingTypeStubs] from hubspot.crm.contacts import SimplePublicObjectInputForCreate # type: ignore[reportMissingTypeStubs] -from domain.abri.models import PlaceRef +from domain.abri.models import AbriRequestRejected, PlaceRef from etl.hubspot.deal_contacts_client import HubspotDealContactsClient from infrastructure.abri.abri_client import AbriClient from infrastructure.abri.config import AbriConfig +from infrastructure.abri.errors import AbriResponseParseError, AbriTransportError from orchestration.tenant_data_sync_orchestrator import ( TenantDataSyncOrchestrator, TenantDataSyncSummary, @@ -330,3 +332,91 @@ def test_run_still_creates_a_contact_for_an_all_blank_signatory( properties={"is_vulnerable": "false"} ) ) + + +# --- unusual but valid outcomes --- + + +def test_run_treats_a_tenancy_with_no_signatories_as_a_valid_zero_contact_outcome( + orchestrator: TenantDataSyncOrchestrator, + mock_session: MagicMock, + sdk_client: MagicMock, +) -> None: + # Arrange + mock_session.post.return_value.content = _tenancy_response("") + + # Act + result = orchestrator.run(PLACE_REF, deal_id=DEAL_ID) + + # Assert + assert result == TenantDataSyncSummary( + tenancy_reference="10042020051017", + contact_ids=(), + vulnerable_contact_count=0, + ) + assert sdk_client.mock_calls == [] + + +# --- rejection passthrough --- + + +def test_run_returns_the_abri_rejection_verbatim_and_makes_no_hubspot_calls( + orchestrator: TenantDataSyncOrchestrator, + mock_session: MagicMock, + sdk_client: MagicMock, +) -> None: + # Arrange + mock_session.post.return_value.content = _load_fixture( + "abri_relay_getscstenantdata_relayerror_response.xml" + ) + + # Act + result = orchestrator.run(PLACE_REF, deal_id=DEAL_ID) + + # Assert + assert isinstance(result, AbriRequestRejected) + assert result.code == "RelayError" + assert result.message.startswith("No Tenancy Found for Query") + assert sdk_client.mock_calls == [] + + +# --- ambiguous responses are retriable, never success or rejection --- + + +@pytest.mark.parametrize( + "body", + [ + b"not xml at all <<<", + b"", # no Tenancy element + b"A1B2", # multiple + b'', + b"", + ], +) +def test_run_raises_a_retriable_parse_error_on_an_unexpectedly_shaped_response( + orchestrator: TenantDataSyncOrchestrator, + mock_session: MagicMock, + sdk_client: MagicMock, + body: bytes, +) -> None: + # Arrange + mock_session.post.return_value.content = body + + # Act / Assert + with pytest.raises(AbriResponseParseError): + orchestrator.run(PLACE_REF, deal_id=DEAL_ID) + assert sdk_client.mock_calls == [] + + +def test_run_raises_a_transport_error_when_the_relay_is_unreachable( + orchestrator: TenantDataSyncOrchestrator, + mock_session: MagicMock, + sdk_client: MagicMock, +) -> None: + # Arrange + mock_session.post.side_effect = requests.ConnectionError("connection refused") + + # Act / Assert + with pytest.raises(AbriTransportError): + orchestrator.run(PLACE_REF, deal_id=DEAL_ID) + assert sdk_client.mock_calls == []