diff --git a/backend/magic_plan/handler.py b/backend/magic_plan/handler.py new file mode 100644 index 00000000..2629c810 --- /dev/null +++ b/backend/magic_plan/handler.py @@ -0,0 +1,28 @@ +from typing import Any + +from backend.app.config import get_settings +from backend.magic_plan.magic_plan_client import MagicPlanClient +from backend.magic_plan.magic_plan_service import MagicPlanService +from backend.magic_plan.magic_plan_trigger_request import MagicPlanTriggerRequest +from backend.utils.subtasks import task_handler +from utils.logger import setup_logger + +logger = setup_logger() + + +@task_handler() +def handler(body: dict[str, Any], context: Any) -> str: + settings = get_settings() + payload = MagicPlanTriggerRequest.model_validate(body) + client = MagicPlanClient( + customer_id=settings.MAGICPLAN_CUSTOMER_ID, + api_key=settings.MAGICPLAN_API_KEY, + ) + plan = MagicPlanService(client).run(payload.address, payload.uprn) + logger.info("Saved MagicPlan plan uid=%s", plan.uid) + return plan.uid + + +if __name__ == "__main__": + event = {"Records": [{"body": '{"address": "2 Laburnum Way Bromley BR2 8BZ"}', "messageId": "local-test"}]} + handler(event, None) diff --git a/backend/magic_plan/tests/test_handler.py b/backend/magic_plan/tests/test_handler.py new file mode 100644 index 00000000..366f3ded --- /dev/null +++ b/backend/magic_plan/tests/test_handler.py @@ -0,0 +1,103 @@ +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest +from pydantic import ValidationError + +from backend.magic_plan.handler import handler + +ADDRESS = "2 Laburnum Way Bromley BR2 8BZ" +PLAN_UID = "a7285ed1-878d-47eb-8aa6-85ef9e187516" + + +def _make_settings(**overrides: str) -> MagicMock: + settings = MagicMock() + settings.MAGICPLAN_CUSTOMER_ID = overrides.get("customer_id", "cust-123") + settings.MAGICPLAN_API_KEY = overrides.get("api_key", "key-abc") + return settings + + +def _call_handler(body: dict[str, Any]) -> Any: + return handler.__wrapped__(body, None) # type: ignore[attr-defined] + + +@pytest.fixture() +def mock_plan() -> MagicMock: + plan = MagicMock() + plan.uid = PLAN_UID + return plan + + +@pytest.fixture() +def mock_service(mock_plan: MagicMock) -> MagicMock: + service = MagicMock() + service.run.return_value = mock_plan + return service + + +# --- request validation --- + + +def test_handler_raises_on_missing_address(mock_plan: MagicMock) -> None: + # Arrange + body: dict[str, Any] = {} + with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \ + patch("backend.magic_plan.handler.MagicPlanClient"), \ + patch("backend.magic_plan.handler.MagicPlanService"): + # Act / Assert + with pytest.raises(ValidationError): + _call_handler(body) + + +# --- client construction --- + + +def test_handler_constructs_client_from_settings(mock_service: MagicMock) -> None: + # Arrange + body = {"address": ADDRESS} + with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings(customer_id="cust-xyz", api_key="key-xyz")), \ + patch("backend.magic_plan.handler.MagicPlanClient") as MockClient, \ + patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): + # Act + _call_handler(body) + # Assert + MockClient.assert_called_once_with(customer_id="cust-xyz", api_key="key-xyz") + + +# --- service orchestration --- + + +def test_handler_calls_service_run_with_address(mock_service: MagicMock) -> None: + # Arrange + body = {"address": ADDRESS} + with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \ + patch("backend.magic_plan.handler.MagicPlanClient"), \ + patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): + # Act + _call_handler(body) + # Assert + mock_service.run.assert_called_once_with(ADDRESS, None) + + +def test_handler_passes_uprn_to_service(mock_service: MagicMock) -> None: + # Arrange + body = {"address": ADDRESS, "uprn": "100023336956"} + with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \ + patch("backend.magic_plan.handler.MagicPlanClient"), \ + patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): + # Act + _call_handler(body) + # Assert + mock_service.run.assert_called_once_with(ADDRESS, "100023336956") + + +def test_handler_returns_plan_uid(mock_service: MagicMock) -> None: + # Arrange + body = {"address": ADDRESS} + with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \ + patch("backend.magic_plan.handler.MagicPlanClient"), \ + patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): + # Act + result = _call_handler(body) + # Assert + assert result == PLAN_UID