From 84822c322827e67ee8665513f1874cb9ec5c0407 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 24 Jul 2026 15:40:20 +0000 Subject: [PATCH] =?UTF-8?q?Log=20an=20Abri=20job=20only=20when=20both=20su?= =?UTF-8?q?rvey=20date=20and=20surveyor=20complete=20the=20pair=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- etl/hubspot/tests/test_abri_flow_triggers.py | 172 +++++++++++++++++-- 1 file changed, 156 insertions(+), 16 deletions(-) diff --git a/etl/hubspot/tests/test_abri_flow_triggers.py b/etl/hubspot/tests/test_abri_flow_triggers.py index 1e0ecdf52..72fe6a1b8 100644 --- a/etl/hubspot/tests/test_abri_flow_triggers.py +++ b/etl/hubspot/tests/test_abri_flow_triggers.py @@ -40,9 +40,71 @@ def make_new_deal(**overrides: str) -> Dict[str, str]: # ========================== -def test_a_first_confirmed_survey_date_builds_a_log_job_message() -> None: - # Arrange - old_deal = make_old_deal(project_code=ABRI_PROJECT_CODE, confirmed_survey_date=None) +def test_a_surveyor_landing_after_the_date_completes_the_pair_and_logs() -> None: + # Arrange: the date was already set; the surveyor lands second, completing + # the pair, so the job is logged (not amended) whichever field lands last. + old_deal = make_old_deal( + project_code=ABRI_PROJECT_CODE, + confirmed_survey_date=datetime(2026, 6, 24, tzinfo=timezone.utc), + confirmed_survey_time="14:30", + third_party_surveyor_identifier=None, + ) + new_deal = make_new_deal( + confirmed_survey_date="2026-06-24", + confirmed_survey_time="14:30", + third_party_surveyor_identifier="THIRDPARTY", + ) + + # Act + message = HubspotDealDiffer.check_abri_triggers_and_construct_message( + hubspot_deal_id=DEAL_ID, + new_deal=new_deal, + new_project=None, + new_listing=LISTING, + old_deal=old_deal, + ) + + # Assert + assert message is not None + assert message["flows"] == ["log_job"] + + +def test_a_date_landing_after_the_surveyor_completes_the_pair_and_logs() -> None: + # Arrange: the surveyor was already set; the date lands second, completing + # the pair, so the job is logged — field ordering does not matter. + old_deal = make_old_deal( + project_code=ABRI_PROJECT_CODE, + confirmed_survey_date=None, + third_party_surveyor_identifier="THIRDPARTY", + ) + new_deal = make_new_deal( + confirmed_survey_date="2026-06-24", + confirmed_survey_time="14:30", + third_party_surveyor_identifier="THIRDPARTY", + ) + + # Act + message = HubspotDealDiffer.check_abri_triggers_and_construct_message( + hubspot_deal_id=DEAL_ID, + new_deal=new_deal, + new_project=None, + new_listing=LISTING, + old_deal=old_deal, + ) + + # Assert + assert message is not None + assert message["flows"] == ["log_job"] + + +def test_a_lone_survey_date_without_a_surveyor_fires_nothing() -> None: + # Arrange: only the date lands; the pair is incomplete, so the job is not + # logged (the resource requirement is kept) and nothing is amended. + old_deal = make_old_deal( + project_code=ABRI_PROJECT_CODE, + confirmed_survey_date=None, + third_party_surveyor_identifier=None, + ) new_deal = make_new_deal( confirmed_survey_date="2026-06-24", confirmed_survey_time="14:30" ) @@ -57,17 +119,88 @@ def test_a_first_confirmed_survey_date_builds_a_log_job_message() -> None: ) # Assert - assert message == { - "hubspot_deal_id": DEAL_ID, - "flows": ["log_job"], - "place_ref": "1007165", - "deal_name": "49 Admers Crescent", - "confirmed_survey_date": "2026-06-24", - "confirmed_survey_time": "14:30", - "last_submission_date": None, - "outcome": None, - "third_party_surveyor_identifier": None, - } + assert message is None + + +def test_a_lone_surveyor_without_a_survey_date_fires_nothing() -> None: + # Arrange: only the surveyor lands; the pair is incomplete, so nothing fires. + old_deal = make_old_deal( + project_code=ABRI_PROJECT_CODE, + confirmed_survey_date=None, + third_party_surveyor_identifier=None, + ) + new_deal = make_new_deal(third_party_surveyor_identifier="THIRDPARTY") + + # Act + message = HubspotDealDiffer.check_abri_triggers_and_construct_message( + hubspot_deal_id=DEAL_ID, + new_deal=new_deal, + new_project=None, + new_listing=LISTING, + old_deal=old_deal, + ) + + # Assert + assert message is None + + +def test_a_pair_already_complete_before_does_not_re_log() -> None: + # Arrange: both fields were already set and nothing changed, so the log + # must not fire a second time. + old_deal = make_old_deal( + project_code=ABRI_PROJECT_CODE, + confirmed_survey_date=datetime(2026, 6, 24, tzinfo=timezone.utc), + confirmed_survey_time="14:30", + third_party_surveyor_identifier="THIRDPARTY", + ) + new_deal = make_new_deal( + confirmed_survey_date="2026-06-24", + confirmed_survey_time="14:30", + third_party_surveyor_identifier="THIRDPARTY", + ) + + # Act + message = HubspotDealDiffer.check_abri_triggers_and_construct_message( + hubspot_deal_id=DEAL_ID, + new_deal=new_deal, + new_project=None, + new_listing=LISTING, + old_deal=old_deal, + ) + + # Assert + assert message is None + + +def test_a_deal_that_already_has_a_client_booking_reference_is_never_logged() -> None: + # Arrange: the pair completes this scrape, but the deal already carries a + # Job Number, so the job exists and must not be logged again. + old_deal = make_old_deal( + project_code=ABRI_PROJECT_CODE, + confirmed_survey_date=datetime(2026, 6, 24, tzinfo=timezone.utc), + confirmed_survey_time="14:30", + third_party_surveyor_identifier=None, + client_booking_reference="AD0226519", + ) + new_deal = make_new_deal( + confirmed_survey_date="2026-06-24", + confirmed_survey_time="14:30", + third_party_surveyor_identifier="THIRDPARTY", + client_booking_reference="AD0226519", + ) + + # Act + message = HubspotDealDiffer.check_abri_triggers_and_construct_message( + hubspot_deal_id=DEAL_ID, + new_deal=new_deal, + new_project=None, + new_listing=LISTING, + old_deal=old_deal, + ) + + # Assert + assert message is not None + assert "log_job" not in message["flows"] def test_the_message_carries_the_deals_third_party_surveyor_identifier() -> None: @@ -384,8 +517,15 @@ def test_a_deal_crossing_into_abandonment_builds_an_abandon_job_message() -> Non def test_a_missing_listing_still_builds_the_message_without_a_place_ref() -> None: # Arrange: validation in the abri lambda is the visibility surface for # a fired flow that lacks its place_ref; the scraper still sends. - old_deal = make_old_deal(project_code=ABRI_PROJECT_CODE, confirmed_survey_date=None) - new_deal = make_new_deal(confirmed_survey_date="2026-06-24") + old_deal = make_old_deal( + project_code=ABRI_PROJECT_CODE, + confirmed_survey_date=None, + third_party_surveyor_identifier=None, + ) + new_deal = make_new_deal( + confirmed_survey_date="2026-06-24", + third_party_surveyor_identifier="THIRDPARTY", + ) # Act message = HubspotDealDiffer.check_abri_triggers_and_construct_message(