mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Dispatch the abandon flow last for a fired deal-change 🟥
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
db5ff971d8
commit
ecdcf12050
1 changed files with 99 additions and 1 deletions
|
|
@ -5,11 +5,19 @@ import pytest
|
|||
|
||||
from applications.abri.abri_trigger_request import AbriTriggerRequest
|
||||
from applications.abri.dispatch import AbriFlowRejectedError, dispatch_abri_flows
|
||||
from domain.abri.models import AbriRequestRejected, AppointmentAmended, PlaceRef
|
||||
from domain.abri.models import (
|
||||
AbriRequestRejected,
|
||||
AppointmentAmended,
|
||||
JobAbandoned,
|
||||
PlaceRef,
|
||||
)
|
||||
from orchestration.abri_orchestrator import (
|
||||
AbandonJobOrchestrationResult,
|
||||
AmendJobOrchestrationResult,
|
||||
AppointmentChange,
|
||||
ConfirmedSurveyBooking,
|
||||
DealAbandonment,
|
||||
JobNoNotYetRecordedError,
|
||||
LogJobOrchestrationResult,
|
||||
LogJobSummary,
|
||||
LogJobWriteBackError,
|
||||
|
|
@ -30,6 +38,7 @@ def _request(flows: List[str]) -> AbriTriggerRequest:
|
|||
"deal_name": "49 Admers Crescent",
|
||||
"confirmed_survey_date": "2026-06-24",
|
||||
"confirmed_survey_time": "14:30",
|
||||
"outcome": "no answer",
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -53,6 +62,10 @@ class FakeOrchestrator:
|
|||
contact_ids=("101", "102"),
|
||||
vulnerable_contact_count=1,
|
||||
)
|
||||
self.abandon_outcome: AbandonJobOrchestrationResult = JobAbandoned(
|
||||
job_no="AC0439951"
|
||||
)
|
||||
self.abandon_error: Optional[Exception] = None
|
||||
|
||||
def amend_job(self, change: AppointmentChange) -> AmendJobOrchestrationResult:
|
||||
self.calls.append(("amend_job", change))
|
||||
|
|
@ -70,6 +83,14 @@ class FakeOrchestrator:
|
|||
self.calls.append(("sync_tenant_data", place_ref, deal_id))
|
||||
return self.sync_outcome
|
||||
|
||||
def abandon_job(
|
||||
self, abandonment: DealAbandonment
|
||||
) -> AbandonJobOrchestrationResult:
|
||||
self.calls.append(("abandon_job", abandonment))
|
||||
if self.abandon_error is not None:
|
||||
raise self.abandon_error
|
||||
return self.abandon_outcome
|
||||
|
||||
|
||||
class FakeDealDatabase:
|
||||
"""In-memory stand-in for the deal-database gateway."""
|
||||
|
|
@ -233,6 +254,70 @@ def test_a_log_write_back_failure_raises_a_non_retriable_error(
|
|||
assert [call[0] for call in orchestrator.calls] == ["log_job"]
|
||||
|
||||
|
||||
# --- abandon runs last, after amend, log and tenant sync ---
|
||||
|
||||
|
||||
def test_abandon_runs_last_in_the_fixed_dispatch_order(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange
|
||||
request = _request(["abandon_job", "sync_tenant_data", "log_job", "amend_job"])
|
||||
|
||||
# Act
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
|
||||
# Assert
|
||||
assert [call[0] for call in orchestrator.calls] == [
|
||||
"amend_job",
|
||||
"log_job",
|
||||
"sync_tenant_data",
|
||||
"abandon_job",
|
||||
]
|
||||
|
||||
|
||||
def test_the_abandon_flow_receives_the_deal_id_and_outcome_from_the_message(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange
|
||||
request = _request(["abandon_job"])
|
||||
|
||||
# Act
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
|
||||
# Assert
|
||||
assert orchestrator.calls == [
|
||||
("abandon_job", DealAbandonment(deal_id=DEAL_ID, outcome="no answer"))
|
||||
]
|
||||
|
||||
|
||||
def test_an_abandon_rejection_raises_so_the_task_fails(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange
|
||||
request = _request(["abandon_job"])
|
||||
orchestrator.abandon_outcome = AbriRequestRejected(
|
||||
code="RelayError", message="Job_no AC0439951 is invalid. Job not found."
|
||||
)
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(AbriFlowRejectedError) as exc_info:
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
assert "abandon_job" in str(exc_info.value)
|
||||
assert "RelayError" in str(exc_info.value)
|
||||
|
||||
|
||||
def test_an_abandon_before_the_job_no_is_recorded_propagates_the_retriable_error(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange
|
||||
request = _request(["abandon_job"])
|
||||
orchestrator.abandon_error = JobNoNotYetRecordedError(deal_id=DEAL_ID)
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(JobNoNotYetRecordedError):
|
||||
dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
|
||||
|
||||
# --- the task output is a PII-free summary of what ran ---
|
||||
|
||||
|
||||
|
|
@ -252,3 +337,16 @@ def test_dispatch_returns_a_pii_free_summary_of_what_ran(
|
|||
"log_job": "skipped: job AC0439951 already logged",
|
||||
"sync_tenant_data": "2 contacts created (1 vulnerable)",
|
||||
}
|
||||
|
||||
|
||||
def test_the_summary_records_an_abandoned_job(
|
||||
orchestrator: FakeOrchestrator, deal_database: FakeDealDatabase
|
||||
) -> None:
|
||||
# Arrange
|
||||
request = _request(["abandon_job"])
|
||||
|
||||
# Act
|
||||
summary = dispatch_abri_flows(request, orchestrator, deal_database)
|
||||
|
||||
# Assert
|
||||
assert summary == {"abandon_job": "job AC0439951 abandoned"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue