Merge pull request #1072 from Hestia-Homes/feature/magicplan-trigger

Magicplan: corrections to trigger and handler
This commit is contained in:
Daniel Roth 2026-05-12 15:23:26 +01:00 committed by GitHub
commit adecfda28f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 74 additions and 24 deletions

View file

@ -20,7 +20,9 @@ def handler(body: dict[str, Any], context: Any) -> str:
api_key=settings.MAGICPLAN_API_KEY, api_key=settings.MAGICPLAN_API_KEY,
) )
# TODO: read s3_bucket from env var so staging/prod use the correct bucket # TODO: read s3_bucket from env var so staging/prod use the correct bucket
plan: Plan = MagicPlanService(client, s3_bucket="retrofit-energy-assessments-dev").run(payload) plan: Plan = MagicPlanService(
client, s3_bucket="retrofit-energy-assessments-dev"
).run(payload)
logger.info("Saved MagicPlan plan uid=%s", plan.uid) logger.info("Saved MagicPlan plan uid=%s", plan.uid)
return plan.uid return plan.uid
@ -30,7 +32,6 @@ if __name__ == "__main__":
"Records": [ "Records": [
{ {
"body": '{"address": "2 Laburnum Way Bromley BR2 8BZ", "hubspot_deal_id": "local-test-deal"}', "body": '{"address": "2 Laburnum Way Bromley BR2 8BZ", "hubspot_deal_id": "local-test-deal"}',
"messageId": "local-test",
} }
] ]
} }

View file

@ -5,3 +5,7 @@ sqlmodel
psycopg2-binary==2.9.10 psycopg2-binary==2.9.10
pydantic-settings==2.6.0 pydantic-settings==2.6.0
boto3==1.35.44 boto3==1.35.44
pytz==2024.2
pandas==2.2.2
numpy==2.1.2

View file

@ -0,0 +1,11 @@
version: "3.9"
services:
ecmk-fetcher-lambda:
build:
context: ../../../
dockerfile: backend/magic_plan/handler/Dockerfile
ports:
- "9000:8080"
env_file:
- ../../../.env

View file

@ -0,0 +1,29 @@
#!/usr/bin/env python3
import json
import requests
HOST = "localhost"
PORT = "9000"
LAMBDA_URL = f"http://{HOST}:{PORT}/2015-03-31/functions/function/invocations"
payload = {
"Records": [
{
"messageId": "test-message-id",
"body": json.dumps(
# {
# "address": "2 Laburnum Way, Rombley, BR2 8BZ | Retrofit Assessment",
# "hubspot_deal_id": "500262906061",
# }
{"address": "33 Wallaby Way, Sydney", "hubspot_deal_id": "123456789"}
),
}
]
}
response = requests.post(LAMBDA_URL, json=payload)
print("Status code:", response.status_code)
print("Response:")
print(response.text)

View file

@ -7,12 +7,11 @@ _BASE_URL = "https://cloud.magicplan.app/api/v2"
class MagicPlanClient: class MagicPlanClient:
def __init__(self, customer_id: str, api_key: str) -> None: def __init__(self, customer_id: str, api_key: str) -> None:
self._api_key = api_key
self._session = requests.Session() self._session = requests.Session()
self._session.headers.update({"customer": customer_id}) self._session.headers.update({"customer": customer_id, "key": api_key})
def get_plans(self) -> PlansListResponse: def get_plans(self) -> PlansListResponse:
r = self._session.get(f"{_BASE_URL}/plans", params={"key": self._api_key}) r = self._session.get(f"{_BASE_URL}/plans")
r.raise_for_status() r.raise_for_status()
return PlansListResponse.model_validate(r.json()["data"]) return PlansListResponse.model_validate(r.json()["data"])
@ -23,8 +22,6 @@ class MagicPlanClient:
return self._fetch_plan(plan_id).content return self._fetch_plan(plan_id).content
def _fetch_plan(self, plan_id: str) -> requests.Response: def _fetch_plan(self, plan_id: str) -> requests.Response:
r = self._session.get( r = self._session.get(f"{_BASE_URL}/plans/{plan_id}")
f"{_BASE_URL}/plans/{plan_id}", params={"key": self._api_key}
)
r.raise_for_status() r.raise_for_status()
return r return r

View file

@ -54,7 +54,7 @@ def test_handler_raises_on_missing_address(mock_plan: MagicMock) -> None:
def test_handler_constructs_client_from_settings(mock_service: MagicMock) -> None: def test_handler_constructs_client_from_settings(mock_service: MagicMock) -> None:
# Arrange # Arrange
body = {"address": ADDRESS} body = {"address": ADDRESS, "hubspot_deal_id": "deal-123"}
with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings(customer_id="cust-xyz", api_key="key-xyz")), \ 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.MagicPlanClient") as MockClient, \
patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service):
@ -69,31 +69,37 @@ def test_handler_constructs_client_from_settings(mock_service: MagicMock) -> Non
def test_handler_calls_service_run_with_address(mock_service: MagicMock) -> None: def test_handler_calls_service_run_with_address(mock_service: MagicMock) -> None:
# Arrange # Arrange
body = {"address": ADDRESS} body = {"address": ADDRESS, "hubspot_deal_id": "deal-123"}
with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \ with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \
patch("backend.magic_plan.handler.MagicPlanClient"), \ patch("backend.magic_plan.handler.MagicPlanClient"), \
patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service):
# Act # Act
_call_handler(body) _call_handler(body)
# Assert # Assert
mock_service.run.assert_called_once_with(ADDRESS, None) mock_service.run.assert_called_once()
request = mock_service.run.call_args.args[0]
assert request.address == ADDRESS
assert request.uprn is None
def test_handler_passes_uprn_to_service(mock_service: MagicMock) -> None: def test_handler_passes_uprn_to_service(mock_service: MagicMock) -> None:
# Arrange # Arrange
body = {"address": ADDRESS, "uprn": "100023336956"} body = {"address": ADDRESS, "uprn": "100023336956", "hubspot_deal_id": "deal-123"}
with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \ with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \
patch("backend.magic_plan.handler.MagicPlanClient"), \ patch("backend.magic_plan.handler.MagicPlanClient"), \
patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service):
# Act # Act
_call_handler(body) _call_handler(body)
# Assert # Assert
mock_service.run.assert_called_once_with(ADDRESS, "100023336956") mock_service.run.assert_called_once()
request = mock_service.run.call_args.args[0]
assert request.address == ADDRESS
assert request.uprn == "100023336956"
def test_handler_returns_plan_uid(mock_service: MagicMock) -> None: def test_handler_returns_plan_uid(mock_service: MagicMock) -> None:
# Arrange # Arrange
body = {"address": ADDRESS} body = {"address": ADDRESS, "hubspot_deal_id": "deal-123"}
with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \ with patch("backend.magic_plan.handler.get_settings", return_value=_make_settings()), \
patch("backend.magic_plan.handler.MagicPlanClient"), \ patch("backend.magic_plan.handler.MagicPlanClient"), \
patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service): patch("backend.magic_plan.handler.MagicPlanService", return_value=mock_service):

View file

@ -20,6 +20,7 @@ def _load_fixture(name: str) -> dict[str, Any]:
def _make_client(mock_session: MagicMock) -> MagicPlanClient: def _make_client(mock_session: MagicMock) -> MagicPlanClient:
mock_session.headers = {}
with patch( with patch(
"backend.magic_plan.magic_plan_client.requests.Session", "backend.magic_plan.magic_plan_client.requests.Session",
return_value=mock_session, return_value=mock_session,
@ -44,7 +45,14 @@ def test_customer_header_set_on_session(mock_session: MagicMock) -> None:
# Act # Act
_make_client(mock_session) _make_client(mock_session)
# Assert # Assert
mock_session.headers.update.assert_called_once_with({"customer": CUSTOMER_ID}) assert mock_session.headers["customer"] == CUSTOMER_ID
def test_api_key_header_set_on_session(mock_session: MagicMock) -> None:
# Act
_make_client(mock_session)
# Assert
assert mock_session.headers["key"] == API_KEY
# --- get_plans --- # --- get_plans ---
@ -62,9 +70,7 @@ def test_get_plans_calls_correct_url(
# Act # Act
client.get_plans() client.get_plans()
# Assert # Assert
mock_session.get.assert_called_once_with( mock_session.get.assert_called_once_with(f"{BASE_URL}/plans")
f"{BASE_URL}/plans", params={"key": API_KEY}
)
def test_get_plans_calls_raise_for_status( def test_get_plans_calls_raise_for_status(
@ -126,9 +132,7 @@ def test_get_plan_calls_correct_url(
# Act # Act
client.get_plan(plan_id) client.get_plan(plan_id)
# Assert # Assert
mock_session.get.assert_called_once_with( mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/{plan_id}")
f"{BASE_URL}/plans/{plan_id}", params={"key": API_KEY}
)
def test_get_plan_calls_raise_for_status( def test_get_plan_calls_raise_for_status(
@ -198,9 +202,7 @@ def test_get_plan_raw_calls_correct_url(
# Act # Act
client.get_plan_raw(plan_id) client.get_plan_raw(plan_id)
# Assert # Assert
mock_session.get.assert_called_once_with( mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/{plan_id}")
f"{BASE_URL}/plans/{plan_id}", params={"key": API_KEY}
)
def test_get_plan_raw_calls_raise_for_status( def test_get_plan_raw_calls_raise_for_status(