mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Leave an already-flagged deal alone and fail loudly on a failed flag 🟩
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a397eba48d
commit
b77769fdca
2 changed files with 82 additions and 5 deletions
|
|
@ -1,7 +1,8 @@
|
|||
from typing import Any, Dict, Optional, cast
|
||||
from typing import Dict, Optional, cast
|
||||
|
||||
from hubspot.client import Client # type: ignore[reportMissingTypeStubs]
|
||||
from hubspot.crm.deals import SimplePublicObjectInput # type: ignore[reportMissingTypeStubs]
|
||||
from hubspot.crm.objects.models import SimplePublicObject as HubspotObject # type: ignore[reportMissingTypeStubs]
|
||||
|
||||
from infrastructure.hubspot.scrubbed_calls import scrubbed_call_with_retry
|
||||
|
||||
|
|
@ -24,12 +25,17 @@ class HubspotDealPropertiesClient:
|
|||
a property whole (HubSpot has no append) would otherwise overwrite
|
||||
edits made since the last scrape.
|
||||
"""
|
||||
deal: Any = scrubbed_call_with_retry(
|
||||
lambda: self._client.crm.deals.basic_api.get_by_id( # type: ignore[reportUnknownMemberType]
|
||||
deal_id, properties=[property_name]
|
||||
deal: HubspotObject = scrubbed_call_with_retry(
|
||||
lambda: cast(
|
||||
HubspotObject,
|
||||
self._client.crm.deals.basic_api.get_by_id( # type: ignore[reportUnknownMemberType]
|
||||
deal_id, properties=[property_name]
|
||||
),
|
||||
)
|
||||
)
|
||||
properties = cast(Dict[str, Optional[str]], deal.properties)
|
||||
properties: Dict[str, Optional[str]] = cast(
|
||||
Dict[str, Optional[str]], deal.properties # type: ignore[reportUnknownMemberType]
|
||||
)
|
||||
return properties.get(property_name)
|
||||
|
||||
def write_deal_property(
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ import requests
|
|||
from hubspot.crm.associations.v4 import AssociationSpec # type: ignore[reportMissingTypeStubs]
|
||||
from hubspot.crm.contacts import ApiException as ContactsApiException # type: ignore[reportMissingTypeStubs]
|
||||
from hubspot.crm.contacts import SimplePublicObjectInputForCreate # type: ignore[reportMissingTypeStubs]
|
||||
from hubspot.crm.deals import ApiException as DealsApiException # type: ignore[reportMissingTypeStubs]
|
||||
from hubspot.crm.deals import SimplePublicObjectInput as DealPropertyInput # type: ignore[reportMissingTypeStubs]
|
||||
|
||||
from domain.abri.models import AbriRequestRejected, PlaceRef
|
||||
from infrastructure.hubspot.deal_contacts_client import HubspotDealContactsClient
|
||||
from infrastructure.hubspot.errors import HubspotRequestError
|
||||
from infrastructure.hubspot.deal_properties_client import HubspotDealPropertiesClient
|
||||
from infrastructure.abri.abri_client import AbriClient
|
||||
from infrastructure.abri.config import AbriConfig
|
||||
|
|
@ -588,3 +590,72 @@ def test_a_void_property_is_flagged_at_the_front_of_the_deals_booking_informatio
|
|||
properties={"notes_for_surveyor": "Void || Key safe code 1234"}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_a_void_property_with_no_existing_booking_information_is_flagged_alone(
|
||||
orchestrator: AbriOrchestrator,
|
||||
mock_session: MagicMock,
|
||||
sdk_client: MagicMock,
|
||||
) -> None:
|
||||
# Arrange: HubSpot sends "" for an unset property.
|
||||
mock_session.post.return_value.content = _load_fixture(
|
||||
"abri_relay_getscstenantdata_notenancyfound_response.xml"
|
||||
)
|
||||
sdk_client.crm.deals.basic_api.get_by_id.return_value.properties = {
|
||||
"notes_for_surveyor": ""
|
||||
}
|
||||
|
||||
# Act
|
||||
orchestrator.sync_tenant_data(PLACE_REF, deal_id=DEAL_ID)
|
||||
|
||||
# Assert: no dangling separator on an otherwise empty note.
|
||||
sdk_client.crm.deals.basic_api.update.assert_called_once_with(
|
||||
DEAL_ID,
|
||||
simple_public_object_input=DealPropertyInput(
|
||||
properties={"notes_for_surveyor": "Void ||"}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_a_deal_already_flagged_void_is_left_alone_so_redelivery_never_stacks_flags(
|
||||
orchestrator: AbriOrchestrator,
|
||||
mock_session: MagicMock,
|
||||
sdk_client: MagicMock,
|
||||
) -> None:
|
||||
# Arrange: the state a redelivered message finds.
|
||||
mock_session.post.return_value.content = _load_fixture(
|
||||
"abri_relay_getscstenantdata_notenancyfound_response.xml"
|
||||
)
|
||||
sdk_client.crm.deals.basic_api.get_by_id.return_value.properties = {
|
||||
"notes_for_surveyor": "Void || Key safe code 1234"
|
||||
}
|
||||
|
||||
# Act
|
||||
result = orchestrator.sync_tenant_data(PLACE_REF, deal_id=DEAL_ID)
|
||||
|
||||
# Assert
|
||||
assert result == PropertyReportedVoid(place_ref=PLACE_REF)
|
||||
assert sdk_client.crm.deals.basic_api.update.mock_calls == []
|
||||
|
||||
|
||||
def test_a_failed_void_flag_write_fails_the_sync_rather_than_reporting_success(
|
||||
orchestrator: AbriOrchestrator,
|
||||
mock_session: MagicMock,
|
||||
sdk_client: MagicMock,
|
||||
) -> None:
|
||||
# Arrange: nothing was created for a void property, so the whole flow is
|
||||
# safe to re-run — the failure must reach the caller and be redelivered.
|
||||
mock_session.post.return_value.content = _load_fixture(
|
||||
"abri_relay_getscstenantdata_notenancyfound_response.xml"
|
||||
)
|
||||
sdk_client.crm.deals.basic_api.get_by_id.return_value.properties = {
|
||||
"notes_for_surveyor": ""
|
||||
}
|
||||
api_error = DealsApiException(status=400, reason="Bad Request")
|
||||
api_error.body = json.dumps({"category": "VALIDATION_ERROR"})
|
||||
api_error.headers = {}
|
||||
sdk_client.crm.deals.basic_api.update.side_effect = api_error
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(HubspotRequestError):
|
||||
orchestrator.sync_tenant_data(PLACE_REF, deal_id=DEAL_ID)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue