mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
153 lines
5.8 KiB
Python
153 lines
5.8 KiB
Python
"""Manual smoke test for the Abri tenant-data -> HubSpot sync (issue #1467).
|
|
|
|
Drives the real AbriOrchestrator tenant-data flow end-to-end with only the
|
|
Abri HTTP edge stubbed (no Abri API key needed): the canned tenancy XML below flows
|
|
through the real parse, phone-priority selection and vulnerability mapping,
|
|
and the resulting contact writes hit the real HubSpot portal.
|
|
|
|
Usage:
|
|
1. Ensure HUBSPOT_API_KEY is set in backend/.env.
|
|
2. Create a throwaway test deal in the HubSpot UI and paste its id into
|
|
DEAL_ID below.
|
|
3. Run: python scripts/smoke_test_tenant_contacts.py
|
|
4. Check the deal in the UI: two contacts (Daniel Roth, Joe Bloggs), with
|
|
Daniel carrying mobilephone, phone, secondary_phone_number,
|
|
is_vulnerable=true and a two-line vulnerability_description, and Joe
|
|
carrying only a name and is_vulnerable=false.
|
|
5. Clean up: paste the printed contact ids into CONTACT_IDS_TO_ARCHIVE,
|
|
set ARCHIVE = True and re-run, then delete the test deal in the UI.
|
|
|
|
All tenant details below are fictional (Ofcom-reserved number ranges).
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any, List, Optional, cast
|
|
|
|
from dotenv import dotenv_values
|
|
from hubspot.client import Client # type: ignore[reportMissingTypeStubs]
|
|
|
|
from domain.abri.models import AbriRequestRejected, PlaceRef
|
|
from infrastructure.abri.abri_client import AbriClient
|
|
from infrastructure.abri.config import AbriConfig
|
|
from infrastructure.hubspot.deal_contacts_client import HubspotDealContactsClient
|
|
from infrastructure.hubspot.deal_properties_client import HubspotDealPropertiesClient
|
|
from orchestration.abri_orchestrator import AbriOrchestrator
|
|
|
|
DEAL_ID = "485125892321"
|
|
|
|
# Cleanup mode: set ARCHIVE = True and list the contact ids printed by a
|
|
# previous run to archive them instead of creating new ones.
|
|
ARCHIVE = False
|
|
CONTACT_IDS_TO_ARCHIVE: List[str] = []
|
|
|
|
PLACE_REF = PlaceRef("SMOKE1")
|
|
|
|
# Daniel Roth exercises best-of-kind phone selection, the runner-up pick into
|
|
# secondary_phone_number, and the vulnerability fields; Joe Bloggs exercises
|
|
# the always-written is_vulnerable=false with every other property omitted.
|
|
SMOKE_TENANCY_XML = b"""<Root>
|
|
<Tenancy end_date="" start_date="15/12/2008">SMOKETEST0001<Tenant forenames="Daniel" main-contact="yes" person_title="Mr" surname="Roth">9000001<Mobile priority="10">07700900123</Mobile>
|
|
<Mobile priority="20">07700900456</Mobile>
|
|
<Telephone priority="5">01632960123</Telephone>
|
|
<Telephone priority="30">01632960789</Telephone>
|
|
<Vulnerability>Blindness</Vulnerability>
|
|
<Vulnerability>Hard of hearing</Vulnerability>
|
|
</Tenant>
|
|
<Tenant forenames="Joe" main-contact="no" person_title="Mr" surname="Bloggs">9000002</Tenant>
|
|
</Tenancy>
|
|
</Root>"""
|
|
|
|
|
|
class _CannedResponse:
|
|
content = SMOKE_TENANCY_XML
|
|
|
|
def raise_for_status(self) -> None:
|
|
pass
|
|
|
|
|
|
class _StubAbriSession:
|
|
"""Stands in for requests.Session; returns the canned tenancy."""
|
|
|
|
def post(self, url: str, data: bytes) -> _CannedResponse:
|
|
return _CannedResponse()
|
|
|
|
|
|
def _hubspot_sdk_client() -> Client:
|
|
# Read the key directly rather than via backend.app.config.get_settings,
|
|
# whose strict Settings model rejects unrelated extra keys in backend/.env.
|
|
env_file = Path(__file__).parents[1] / "backend" / ".env"
|
|
access_token = os.getenv("HUBSPOT_API_KEY") or dotenv_values(env_file).get(
|
|
"HUBSPOT_API_KEY"
|
|
)
|
|
if not access_token:
|
|
raise SystemExit("Missing HUBSPOT_API_KEY (env var or backend/.env)")
|
|
return Client.create(access_token=access_token) # type: ignore[reportUnknownMemberType]
|
|
|
|
|
|
def _stubbed_abri_client() -> AbriClient:
|
|
client = AbriClient(
|
|
config=AbriConfig(
|
|
endpoint_url="https://stubbed.invalid/relay",
|
|
username="smoke-test",
|
|
password="",
|
|
default_resource="",
|
|
)
|
|
)
|
|
client._session = cast(
|
|
Any, _StubAbriSession()
|
|
) # pyright: ignore[reportPrivateUsage]
|
|
return client
|
|
|
|
|
|
class _UnusedDealDatabase:
|
|
"""Satisfies the gateway dependency; the tenant-data flow never touches it."""
|
|
|
|
def record_job_no(self, deal_id: str, job_no: str) -> None:
|
|
raise AssertionError("tenant-data sync must not touch the deal database")
|
|
|
|
def job_no_for_deal(self, deal_id: str) -> Optional[str]:
|
|
raise AssertionError("tenant-data sync must not touch the deal database")
|
|
|
|
|
|
def _archive_contacts(sdk_client: Client) -> None:
|
|
if not CONTACT_IDS_TO_ARCHIVE:
|
|
raise SystemExit("ARCHIVE is set but CONTACT_IDS_TO_ARCHIVE is empty")
|
|
for contact_id in CONTACT_IDS_TO_ARCHIVE:
|
|
sdk_client.crm.contacts.basic_api.archive(contact_id=contact_id) # type: ignore[reportUnknownMemberType]
|
|
print(f"Archived contact {contact_id}")
|
|
|
|
|
|
def main() -> None:
|
|
sdk_client = _hubspot_sdk_client()
|
|
|
|
if ARCHIVE:
|
|
_archive_contacts(sdk_client)
|
|
return
|
|
|
|
if DEAL_ID == "EDIT-ME":
|
|
raise SystemExit("Set DEAL_ID to a throwaway test deal id first")
|
|
|
|
orchestrator = AbriOrchestrator(
|
|
abri_client=_stubbed_abri_client(),
|
|
deal_contacts=HubspotDealContactsClient(sdk_client=sdk_client),
|
|
deal_properties=HubspotDealPropertiesClient(sdk_client=sdk_client),
|
|
deal_database=_UnusedDealDatabase(),
|
|
)
|
|
|
|
result = orchestrator.sync_tenant_data(PLACE_REF, deal_id=DEAL_ID)
|
|
|
|
if isinstance(result, AbriRequestRejected):
|
|
raise SystemExit(f"Unexpected Abri rejection from stub: {result}")
|
|
|
|
print(f"Tenancy reference: {result.tenancy_reference}")
|
|
print(f"Contacts created: {list(result.contact_ids)}")
|
|
print(f"Vulnerable contact count: {result.vulnerable_contact_count}")
|
|
print(
|
|
"\nTo clean up: paste the ids above into CONTACT_IDS_TO_ARCHIVE, "
|
|
"set ARCHIVE = True and re-run."
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|