Model/etl/hubspot/tests/test_abri_flow_triggers.py
2026-07-07 13:15:40 +00:00

232 lines
6.6 KiB
Python

from datetime import datetime, timezone
from typing import Any, Dict
import uuid
from backend.app.db.models.hubspot_deal_data import HubspotDealData
from etl.hubspot.abri_flow_triggers import check_for_abri_triggers_and_construct_message
BASE_TIME = datetime(2025, 12, 1, 12, 0, 0)
ABRI_PROJECT_CODE = "[Abri Stock Condition - Privately Funded - 062026 - 1247536318670]"
DEAL_ID = "9876543210"
LISTING = {"listing_id": "55", "owner_property_id": "1007165", "national_uprn": "1"}
def make_old_deal(**overrides: Any) -> HubspotDealData:
return HubspotDealData(
id=overrides.get("id", uuid.uuid4()),
deal_id=DEAL_ID,
created_at=BASE_TIME,
updated_at=BASE_TIME,
**{k: v for k, v in overrides.items() if k != "id"},
)
def make_new_deal(**overrides: str) -> Dict[str, str]:
return {
"hs_object_id": DEAL_ID,
"dealname": "49 Admers Crescent",
"project_code": ABRI_PROJECT_CODE,
**overrides,
}
# ==========================
# SINGLE-FLOW FIRING
# ==========================
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)
new_deal = make_new_deal(
confirmed_survey_date="2026-06-24", confirmed_survey_time="14:30"
)
# Act
message = check_for_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 == {
"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",
}
def test_a_changed_confirmed_survey_date_builds_an_amend_job_message() -> None:
# Arrange
old_deal = make_old_deal(
project_code=ABRI_PROJECT_CODE,
confirmed_survey_date=datetime(2026, 6, 18, tzinfo=timezone.utc),
)
new_deal = make_new_deal(
confirmed_survey_date="2026-06-24", confirmed_survey_time="14:30"
)
# Act
message = check_for_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"] == ["amend_job"]
assert message["confirmed_survey_date"] == "2026-06-24"
def test_a_first_expected_commencement_date_builds_a_tenant_sync_message() -> None:
# Arrange
old_deal = make_old_deal(
project_code=ABRI_PROJECT_CODE, expected_commencement_date=None
)
new_deal = make_new_deal(expected_commencement_date="2026-07-01")
# Act
message = check_for_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"] == ["sync_tenant_data"]
assert message["place_ref"] == "1007165"
# ==========================
# ONE MESSAGE PER SCRAPE
# ==========================
def test_several_flows_firing_in_one_scrape_share_one_message() -> None:
# Arrange: date changed and commencement date first set together.
old_deal = make_old_deal(
project_code=ABRI_PROJECT_CODE,
confirmed_survey_date=datetime(2026, 6, 18, tzinfo=timezone.utc),
expected_commencement_date=None,
)
new_deal = make_new_deal(
confirmed_survey_date="2026-06-24",
confirmed_survey_time="14:30",
expected_commencement_date="2026-07-01",
)
# Act
message = check_for_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"] == ["amend_job", "sync_tenant_data"]
# ==========================
# NOTHING FIRES
# ==========================
def test_no_message_when_no_abri_trigger_fires() -> None:
# Arrange: something changed, but nothing Abri cares about.
old_deal = make_old_deal(project_code=ABRI_PROJECT_CODE, outcome=None)
new_deal = make_new_deal(outcome="left voicemail")
# Act
message = check_for_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_no_message_for_a_non_abri_deal() -> None:
# Arrange: a survey date first set, but on another project's deal.
old_deal = make_old_deal(project_code="Other", confirmed_survey_date=None)
new_deal = make_new_deal(project_code="Other", confirmed_survey_date="2026-06-24")
# Act
message = check_for_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_abandonment_alone_stays_unwired_and_builds_no_message() -> None:
# Arrange: the abandonment predicate would fire, but CancelJob does not
# exist yet, so no flow may be dispatched for it.
old_deal = make_old_deal(
project_code=ABRI_PROJECT_CODE, number_of_attempts="2", outcome=None
)
new_deal = make_new_deal(number_of_attempts="3", outcome="no answer")
# Act
message = check_for_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
# ==========================
# PAYLOAD ROBUSTNESS
# ==========================
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")
# Act
message = check_for_abri_triggers_and_construct_message(
hubspot_deal_id=DEAL_ID,
new_deal=new_deal,
new_project=None,
new_listing=None,
old_deal=old_deal,
)
# Assert
assert message is not None
assert message["place_ref"] is None
assert message["confirmed_survey_time"] is None