diff --git a/backend/magic_plan/magic_plan_client.py b/backend/magic_plan/magic_plan_client.py index 2880bf43..bf50a6f8 100644 --- a/backend/magic_plan/magic_plan_client.py +++ b/backend/magic_plan/magic_plan_client.py @@ -1,6 +1,6 @@ import requests -from datatypes.magicplan.api.response import MagicPlanPlan, PlansListResponse +from datatypes.magicplan.api.response import MagicPlanPlan, PlanSummary, PlansListResponse _BASE_URL = "https://cloud.magicplan.app/api/v2" @@ -10,10 +10,18 @@ class MagicPlanClient: self._session = requests.Session() self._session.headers.update({"customer": customer_id, "key": api_key}) - def get_plans(self) -> PlansListResponse: - r = self._session.get(f"{_BASE_URL}/plans") - r.raise_for_status() - return PlansListResponse.model_validate(r.json()["data"]) + def get_plans(self) -> list[PlanSummary]: + all_plans: list[PlanSummary] = [] + page = 1 + while True: + r = self._session.get(f"{_BASE_URL}/workgroups/plans", params={"page": page}) + r.raise_for_status() + response = PlansListResponse.model_validate(r.json()["data"]) + all_plans.extend(response.plans) + if not response.paging.next_page: + break + page += 1 + return all_plans def get_plan(self, plan_id: str) -> MagicPlanPlan: return MagicPlanPlan.model_validate(self._fetch_plan(plan_id).json()["data"]) @@ -22,6 +30,6 @@ class MagicPlanClient: return self._fetch_plan(plan_id).content def _fetch_plan(self, plan_id: str) -> requests.Response: - r = self._session.get(f"{_BASE_URL}/plans/{plan_id}") + r = self._session.get(f"{_BASE_URL}/plans/get/{plan_id}") r.raise_for_status() return r diff --git a/backend/magic_plan/magic_plan_service.py b/backend/magic_plan/magic_plan_service.py index fb0a7610..22e19ddf 100644 --- a/backend/magic_plan/magic_plan_service.py +++ b/backend/magic_plan/magic_plan_service.py @@ -3,11 +3,7 @@ import json from datetime import datetime, timezone from typing import Optional -from datatypes.magicplan.api.response import ( - MagicPlanPlan, - PlanSummary, - PlansListResponse, -) +from datatypes.magicplan.api.response import MagicPlanPlan, PlanSummary from datatypes.magicplan.domain.mapper import map_plan from datatypes.magicplan.domain.models import Plan @@ -39,10 +35,8 @@ class MagicPlanService: if uprn is not None: logger.info("MagicPlanService.run uprn=%s", uprn) - plans_response: PlansListResponse = self._client.get_plans() - matched: Optional[PlanSummary] = find_matching_plan( - plans_response.plans, address - ) + plans: list[PlanSummary] = self._client.get_plans() + matched: Optional[PlanSummary] = find_matching_plan(plans, address) if matched is None: raise ValueError(f"No MagicPlan found for address: {address!r}") diff --git a/backend/magic_plan/tests/test_magic_plan_client.py b/backend/magic_plan/tests/test_magic_plan_client.py index a0827bee..211a5d4d 100644 --- a/backend/magic_plan/tests/test_magic_plan_client.py +++ b/backend/magic_plan/tests/test_magic_plan_client.py @@ -7,7 +7,7 @@ import pytest import requests from backend.magic_plan.magic_plan_client import MagicPlanClient -from datatypes.magicplan.api.response import MagicPlanPlan, PlansListResponse +from datatypes.magicplan.api.response import MagicPlanPlan, PlanSummary FIXTURE_DIR = Path(__file__).parents[2] / "magic_plan" BASE_URL = "https://cloud.magicplan.app/api/v2" @@ -70,7 +70,9 @@ def test_get_plans_calls_correct_url( # Act client.get_plans() # Assert - mock_session.get.assert_called_once_with(f"{BASE_URL}/plans") + mock_session.get.assert_called_once_with( + f"{BASE_URL}/workgroups/plans", params={"page": 1} + ) def test_get_plans_calls_raise_for_status( @@ -88,7 +90,7 @@ def test_get_plans_calls_raise_for_status( mock_session.get.return_value.raise_for_status.assert_called_once() -def test_get_plans_returns_plans_list_response( +def test_get_plans_returns_list_of_plan_summaries( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange @@ -100,8 +102,9 @@ def test_get_plans_returns_plans_list_response( # Act result = client.get_plans() # Assert - assert isinstance(result, PlansListResponse) - assert len(result.plans) == 1 + assert isinstance(result, list) + assert len(result) == 1 + assert isinstance(result[0], PlanSummary) def test_get_plans_propagates_http_error( @@ -116,6 +119,34 @@ def test_get_plans_propagates_http_error( client.get_plans() +def test_get_plans_multi_page_fetches_all_pages( + client: MagicPlanClient, mock_session: MagicMock +) -> None: + # Arrange + page1_plan = _load_fixture("magicplan_api_plans_response_example.json")["data"][ + "plans" + ][0] + page2_plan = {**page1_plan, "id": "page-2-plan-id"} + page1_response = MagicMock() + page1_response.json.return_value = { + "data": {"paging": {"page": 1, "next_page": True, "count": 2}, "plans": [page1_plan]} + } + page2_response = MagicMock() + page2_response.json.return_value = { + "data": {"paging": {"page": 2, "next_page": False, "count": 2}, "plans": [page2_plan]} + } + mock_session.get.side_effect = [page1_response, page2_response] + # Act + result = client.get_plans() + # Assert + assert mock_session.get.call_count == 2 + mock_session.get.assert_any_call(f"{BASE_URL}/workgroups/plans", params={"page": 1}) + mock_session.get.assert_any_call(f"{BASE_URL}/workgroups/plans", params={"page": 2}) + assert len(result) == 2 + assert result[0].id == page1_plan["id"] + assert result[1].id == "page-2-plan-id" + + # --- get_plan --- @@ -132,7 +163,7 @@ def test_get_plan_calls_correct_url( # Act client.get_plan(plan_id) # Assert - mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/{plan_id}") + mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/get/{plan_id}") def test_get_plan_calls_raise_for_status( @@ -202,7 +233,7 @@ def test_get_plan_raw_calls_correct_url( # Act client.get_plan_raw(plan_id) # Assert - mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/{plan_id}") + mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/get/{plan_id}") def test_get_plan_raw_calls_raise_for_status( diff --git a/backend/magic_plan/tests/test_magic_plan_service.py b/backend/magic_plan/tests/test_magic_plan_service.py index f6954824..158cf4d6 100644 --- a/backend/magic_plan/tests/test_magic_plan_service.py +++ b/backend/magic_plan/tests/test_magic_plan_service.py @@ -91,7 +91,7 @@ def test_run_fetches_plan_with_matched_id( domain_plan: Plan, ) -> None: # Arrange - mock_client.get_plans.return_value.plans = [plan_summary] + mock_client.get_plans.return_value = [plan_summary] mock_client.get_plan.return_value = api_magic_plan service = _make_service(mock_client) with patch( @@ -114,7 +114,7 @@ def test_run_returns_mapped_plan( domain_plan: Plan, ) -> None: # Arrange - mock_client.get_plans.return_value.plans = [plan_summary] + mock_client.get_plans.return_value = [plan_summary] mock_client.get_plan.return_value = api_magic_plan service = _make_service(mock_client) with patch( @@ -137,7 +137,7 @@ def test_run_calls_save_plan_with_mapped_plan( plan_summary: PlanSummary, ) -> None: # Arrange - mock_client.get_plans.return_value.plans = [plan_summary] + mock_client.get_plans.return_value = [plan_summary] mock_client.get_plan.return_value = api_magic_plan service = _make_service(mock_client) with patch( @@ -161,7 +161,7 @@ def test_run_accepts_uprn_without_error( plan_summary: PlanSummary, ) -> None: # Arrange - mock_client.get_plans.return_value.plans = [plan_summary] + mock_client.get_plans.return_value = [plan_summary] mock_client.get_plan.return_value = api_magic_plan service = _make_service(mock_client) with patch( @@ -184,7 +184,7 @@ def test_run_uploads_to_s3_with_uprn_key( plan_summary: PlanSummary, ) -> None: # Arrange - mock_client.get_plans.return_value.plans = [plan_summary] + mock_client.get_plans.return_value = [plan_summary] request = _make_request(uprn="100023336956") service = MagicPlanService(client=mock_client, s3_bucket=S3_BUCKET) with patch( @@ -211,7 +211,7 @@ def test_run_uploads_to_s3_with_deal_id_key_when_uprn_absent( plan_summary: PlanSummary, ) -> None: # Arrange - mock_client.get_plans.return_value.plans = [plan_summary] + mock_client.get_plans.return_value = [plan_summary] mock_client.get_plan.return_value = api_magic_plan request = _make_request(hubspot_deal_id="deal-456", uprn=None) service = MagicPlanService(client=mock_client, s3_bucket=S3_BUCKET) @@ -242,7 +242,7 @@ def test_run_creates_uploaded_file_record( plan_summary: PlanSummary, ) -> None: # Arrange - mock_client.get_plans.return_value.plans = [plan_summary] + mock_client.get_plans.return_value = [plan_summary] mock_client.get_plan.return_value = api_magic_plan request = _make_request(hubspot_deal_id="deal-789", uprn="100023336956") service = MagicPlanService(client=mock_client, s3_bucket=S3_BUCKET)