mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Fail non-retriably when amending or abandoning a deal that never logged to Abri 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cf0129103e
commit
fb64f9687e
2 changed files with 84 additions and 2 deletions
|
|
@ -44,6 +44,24 @@ class AbriFlows(Protocol):
|
|||
) -> AbandonJobOrchestrationResult: ...
|
||||
|
||||
|
||||
class DealDivergedFromAbriError(NonRetriableTaskError):
|
||||
"""An amend/abandon on a deal carrying no client_booking_reference.
|
||||
|
||||
An absent Job Number is ambiguous — the job was never logged, or the
|
||||
reference OpenHousing returned was lost — so the change or cancellation
|
||||
cannot be synced to Abri. Non-retriable: redelivery cannot conjure a
|
||||
reference; a human must reconcile the deal against OpenHousing.
|
||||
"""
|
||||
|
||||
def __init__(self, flow: str, deal_id: str) -> None:
|
||||
self.flow = flow
|
||||
self.deal_id = deal_id
|
||||
super().__init__(
|
||||
f"{flow} cannot run for deal {deal_id}: no client_booking_reference "
|
||||
"(Job Number) — the deal has diverged from Abri; check OpenHousing"
|
||||
)
|
||||
|
||||
|
||||
class AbriFlowRejectedError(Exception):
|
||||
"""An OpenHousing rejection, surfaced as a failure so the task fails.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ from typing import Any, Dict, List, Optional
|
|||
import pytest
|
||||
|
||||
from applications.abri.abri_trigger_request import AbriTriggerRequest
|
||||
from applications.abri.dispatch import AbriFlowRejectedError, dispatch_abri_flows
|
||||
from applications.abri.dispatch import (
|
||||
AbriFlowRejectedError,
|
||||
DealDivergedFromAbriError,
|
||||
dispatch_abri_flows,
|
||||
)
|
||||
from domain.abri.models import (
|
||||
AbriRequestRejected,
|
||||
AppointmentAmended,
|
||||
|
|
@ -29,7 +33,9 @@ from utilities.aws_lambda.task_handler import NonRetriableTaskError
|
|||
DEAL_ID = "9876543210"
|
||||
|
||||
|
||||
def _request(flows: List[str]) -> AbriTriggerRequest:
|
||||
def _request(
|
||||
flows: List[str], client_booking_reference: Optional[str] = "AD0226519"
|
||||
) -> AbriTriggerRequest:
|
||||
return AbriTriggerRequest.model_validate(
|
||||
{
|
||||
"hubspot_deal_id": DEAL_ID,
|
||||
|
|
@ -41,6 +47,7 @@ def _request(flows: List[str]) -> AbriTriggerRequest:
|
|||
"last_submission_date": "2026-07-01",
|
||||
"outcome": "no answer",
|
||||
"third_party_surveyor_identifier": "THIRDPARTY",
|
||||
"client_booking_reference": client_booking_reference,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -218,6 +225,63 @@ def test_the_log_flow_runs_when_the_deal_has_no_job_no(
|
|||
assert [call[0] for call in orchestrator.calls] == ["log_job"]
|
||||
|
||||
|
||||
# --- divergence: amend/abandon with no Job Number cannot sync to Abri ---
|
||||
|
||||
|
||||
def test_an_amend_with_no_client_booking_reference_raises_a_divergence_error(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange: an amend fires on a deal that was never logged (no Job Number).
|
||||
request = _request(["amend_job"], client_booking_reference=None)
|
||||
|
||||
# Act / Assert: non-retriable, and the amend flow never runs.
|
||||
with pytest.raises(DealDivergedFromAbriError) as exc_info:
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
assert isinstance(exc_info.value, NonRetriableTaskError)
|
||||
assert DEAL_ID in str(exc_info.value)
|
||||
assert orchestrator.calls == []
|
||||
|
||||
|
||||
def test_an_abandon_with_no_client_booking_reference_raises_a_divergence_error(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange: an abandon fires on a deal with no Job Number — possibly a lost
|
||||
# reference, so a live Abri job must not be silently left un-cancelled.
|
||||
request = _request(["abandon_job"], client_booking_reference=None)
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(DealDivergedFromAbriError) as exc_info:
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
assert isinstance(exc_info.value, NonRetriableTaskError)
|
||||
assert orchestrator.calls == []
|
||||
|
||||
|
||||
def test_an_amend_with_a_client_booking_reference_proceeds(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange: the deal carries a Job Number, so the job exists and is amended.
|
||||
request = _request(["amend_job"], client_booking_reference="AD0226519")
|
||||
|
||||
# Act
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
|
||||
# Assert
|
||||
assert [call[0] for call in orchestrator.calls] == ["amend_job"]
|
||||
|
||||
|
||||
def test_an_abandon_with_a_client_booking_reference_proceeds(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange
|
||||
request = _request(["abandon_job"], client_booking_reference="AD0226519")
|
||||
|
||||
# Act
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
|
||||
# Assert
|
||||
assert [call[0] for call in orchestrator.calls] == ["abandon_job"]
|
||||
|
||||
|
||||
# --- rejections raise, so the task fails and the DLQ is the ops inbox ---
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue