From b22afcde9f4cf6fec856ba1a2798f68b6ea2650b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 3 Jul 2026 14:22:09 +0000 Subject: [PATCH] =?UTF-8?q?Abri=20log=5Fjob=20returns=20the=20OpenHousing?= =?UTF-8?q?=20job=20number=20from=20the=20relay=20success=20response=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 Fable 5 --- domain/abri/__init__.py | 0 domain/abri/models.py | 32 ++++++++ infrastructure/abri/__init__.py | 0 infrastructure/abri/abri_client.py | 13 ++++ infrastructure/abri/config.py | 14 ++++ .../abri_relay_logjob_relayerror_response.xml | 6 ++ .../abri_relay_logjob_request_example.xml | 20 +++++ .../abri_relay_logjob_success_response.xml | 6 ++ tests/infrastructure/abri/__init__.py | 0 tests/infrastructure/abri/test_abri_client.py | 77 +++++++++++++++++++ 10 files changed, 168 insertions(+) create mode 100644 domain/abri/__init__.py create mode 100644 domain/abri/models.py create mode 100644 infrastructure/abri/__init__.py create mode 100644 infrastructure/abri/abri_client.py create mode 100644 infrastructure/abri/config.py create mode 100644 tests/abri/abri_relay_logjob_relayerror_response.xml create mode 100644 tests/abri/abri_relay_logjob_request_example.xml create mode 100644 tests/abri/abri_relay_logjob_success_response.xml create mode 100644 tests/infrastructure/abri/__init__.py create mode 100644 tests/infrastructure/abri/test_abri_client.py diff --git a/domain/abri/__init__.py b/domain/abri/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/domain/abri/models.py b/domain/abri/models.py new file mode 100644 index 000000000..e160c3e3b --- /dev/null +++ b/domain/abri/models.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass +from datetime import date +from typing import Literal, NewType, Union + +PlaceRef = NewType("PlaceRef", str) + +SlotCode = Literal["AM", "PM", "AD"] + + +@dataclass(frozen=True) +class LogJobRequest: + client_ref: str + place_ref: PlaceRef + appointment_date: date + appointment_time: SlotCode + short_description: str + long_description: str + + +@dataclass(frozen=True) +class JobLogged: + job_no: str + logged_info: str + + +@dataclass(frozen=True) +class AbriRequestRejected: + code: str + message: str + + +LogJobResult = Union[JobLogged, AbriRequestRejected] diff --git a/infrastructure/abri/__init__.py b/infrastructure/abri/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/infrastructure/abri/abri_client.py b/infrastructure/abri/abri_client.py new file mode 100644 index 000000000..2579ba341 --- /dev/null +++ b/infrastructure/abri/abri_client.py @@ -0,0 +1,13 @@ +import requests + +from domain.abri.models import LogJobRequest, LogJobResult +from infrastructure.abri.config import AbriConfig + + +class AbriClient: + def __init__(self, config: AbriConfig) -> None: + self._config = config + self._session = requests.Session() + + def log_job(self, request: LogJobRequest) -> LogJobResult: + raise NotImplementedError diff --git a/infrastructure/abri/config.py b/infrastructure/abri/config.py new file mode 100644 index 000000000..fe9fa6d8c --- /dev/null +++ b/infrastructure/abri/config.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass +from typing import Mapping + + +@dataclass(frozen=True) +class AbriConfig: + endpoint_url: str + username: str + password: str + default_resource: str + + @classmethod + def from_env(cls, env: Mapping[str, str]) -> "AbriConfig": + raise NotImplementedError diff --git a/tests/abri/abri_relay_logjob_relayerror_response.xml b/tests/abri/abri_relay_logjob_relayerror_response.xml new file mode 100644 index 000000000..ac6647828 --- /dev/null +++ b/tests/abri/abri_relay_logjob_relayerror_response.xml @@ -0,0 +1,6 @@ + + + false + RelayError + No property was found with UPRN/place_ref '1007176aa', street name '', house number '0', house name '', post code '' + diff --git a/tests/abri/abri_relay_logjob_request_example.xml b/tests/abri/abri_relay_logjob_request_example.xml new file mode 100644 index 000000000..9fe160990 --- /dev/null +++ b/tests/abri/abri_relay_logjob_request_example.xml @@ -0,0 +1,20 @@ + + +
+ +
+ + + + + + + + + + + + + + +
diff --git a/tests/abri/abri_relay_logjob_success_response.xml b/tests/abri/abri_relay_logjob_success_response.xml new file mode 100644 index 000000000..aabd14b40 --- /dev/null +++ b/tests/abri/abri_relay_logjob_success_response.xml @@ -0,0 +1,6 @@ + + + + AD0226519 + + diff --git a/tests/infrastructure/abri/__init__.py b/tests/infrastructure/abri/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/infrastructure/abri/test_abri_client.py b/tests/infrastructure/abri/test_abri_client.py new file mode 100644 index 000000000..40eaed7a2 --- /dev/null +++ b/tests/infrastructure/abri/test_abri_client.py @@ -0,0 +1,77 @@ +from datetime import date +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from domain.abri.models import JobLogged, LogJobRequest, PlaceRef +from infrastructure.abri.abri_client import AbriClient +from infrastructure.abri.config import AbriConfig + +FIXTURE_DIR = Path(__file__).parents[2] / "abri" +ENDPOINT_URL = "https://relay.example.test/api/DomnaRelay?code=test-function-key" + +CONFIG = AbriConfig( + endpoint_url=ENDPOINT_URL, + username="DomnaWeb", + password="", + default_resource="NAULKH", +) + + +def _load_fixture(name: str) -> bytes: + return (FIXTURE_DIR / name).read_bytes() + + +def _make_client(mock_session: MagicMock) -> AbriClient: + with patch( + "infrastructure.abri.abri_client.requests.Session", + return_value=mock_session, + ): + return AbriClient(config=CONFIG) + + +def _spec_example_request() -> LogJobRequest: + return LogJobRequest( + client_ref="DOM51111", + place_ref=PlaceRef("1007165"), + appointment_date=date(2026, 6, 18), + appointment_time="PM", + short_description="Christian's SCS External Test.", + long_description="Christian's SCS External Test.", + ) + + +@pytest.fixture() +def mock_session() -> MagicMock: + return MagicMock() + + +@pytest.fixture() +def client(mock_session: MagicMock) -> AbriClient: + return _make_client(mock_session) + + +# --- log_job: success --- + + +def test_log_job_returns_job_logged_with_openhousing_job_number( + client: AbriClient, mock_session: MagicMock +) -> None: + # Arrange + mock_session.post.return_value.content = _load_fixture( + "abri_relay_logjob_success_response.xml" + ) + + # Act + result = client.log_job(_spec_example_request()) + + # Assert + assert result == JobLogged( + job_no="AD0226519", + logged_info=( + "Job number AD0226519 has been successfully logged for " + "49 Admers Crescent, Liphook, Midsomer, XX99 IOP - " + "Short Description example job." + ), + )