import json from pathlib import Path from typing import Any from unittest.mock import MagicMock, patch import pytest import requests from backend.magic_plan.magic_plan_client import MagicPlanClient from datatypes.magicplan.api.response import MagicPlanPlan, PlanSummary FIXTURE_DIR = Path(__file__).parents[2] / "magic_plan" BASE_URL = "https://cloud.magicplan.app/api/v2" CUSTOMER_ID = "test-customer" API_KEY = "test-key" def _load_fixture(name: str) -> dict[str, Any]: return json.loads((FIXTURE_DIR / name).read_text()) def _make_client(mock_session: MagicMock) -> MagicPlanClient: mock_session.headers = {} with patch( "backend.magic_plan.magic_plan_client.requests.Session", return_value=mock_session, ): return MagicPlanClient(customer_id=CUSTOMER_ID, api_key=API_KEY) @pytest.fixture() def mock_session() -> MagicMock: return MagicMock() @pytest.fixture() def client(mock_session: MagicMock) -> MagicPlanClient: return _make_client(mock_session) # --- constructor --- def test_customer_header_set_on_session(mock_session: MagicMock) -> None: # Act _make_client(mock_session) # Assert 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 --- def test_get_plans_calls_correct_url( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange plans_data = _load_fixture("magicplan_api_plans_response_example.json")["data"] mock_session.get.return_value.json.return_value = { "message": "OK", "data": plans_data, } # Act client.get_plans() # Assert mock_session.get.assert_called_once_with( f"{BASE_URL}/workgroups/plans", params={"page": 1} ) def test_get_plans_calls_raise_for_status( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange plans_data = _load_fixture("magicplan_api_plans_response_example.json")["data"] mock_session.get.return_value.json.return_value = { "message": "OK", "data": plans_data, } # Act client.get_plans() # Assert mock_session.get.return_value.raise_for_status.assert_called_once() def test_get_plans_returns_list_of_plan_summaries( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange plans_data = _load_fixture("magicplan_api_plans_response_example.json")["data"] mock_session.get.return_value.json.return_value = { "message": "OK", "data": plans_data, } # Act result = client.get_plans() # Assert assert isinstance(result, list) assert len(result) == 1 assert isinstance(result[0], PlanSummary) def test_get_plans_propagates_http_error( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError( "404" ) # Act / Assert with pytest.raises(requests.HTTPError): 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 --- def test_get_plan_calls_correct_url( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"] mock_session.get.return_value.json.return_value = { "message": "OK", "data": plan_data, } plan_id = "a7285ed1-878d-47eb-8aa6-85ef9e187516" # Act client.get_plan(plan_id) # Assert mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/get/{plan_id}") def test_get_plan_calls_raise_for_status( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"] mock_session.get.return_value.json.return_value = { "message": "OK", "data": plan_data, } # Act client.get_plan("a7285ed1-878d-47eb-8aa6-85ef9e187516") # Assert mock_session.get.return_value.raise_for_status.assert_called_once() def test_get_plan_returns_magic_plan( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"] mock_session.get.return_value.json.return_value = { "message": "OK", "data": plan_data, } # Act result = client.get_plan("a7285ed1-878d-47eb-8aa6-85ef9e187516") # Assert assert isinstance(result, MagicPlanPlan) assert result.plan.id == "a7285ed1-878d-47eb-8aa6-85ef9e187516" def test_get_plan_propagates_http_error( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError( "500" ) # Act / Assert with pytest.raises(requests.HTTPError): client.get_plan("some-id") # --- get_plan_raw --- def test_get_plan_raw_returns_bytes( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange mock_session.get.return_value.content = b'{"data": "raw"}' plan_id = "a7285ed1-878d-47eb-8aa6-85ef9e187516" # Act result = client.get_plan_raw(plan_id) # Assert assert isinstance(result, bytes) def test_get_plan_raw_calls_correct_url( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange mock_session.get.return_value.content = b"{}" plan_id = "a7285ed1-878d-47eb-8aa6-85ef9e187516" # Act client.get_plan_raw(plan_id) # Assert mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/get/{plan_id}") def test_get_plan_raw_calls_raise_for_status( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange mock_session.get.return_value.content = b"{}" # Act client.get_plan_raw("a7285ed1-878d-47eb-8aa6-85ef9e187516") # Assert mock_session.get.return_value.raise_for_status.assert_called_once() def test_get_plan_raw_propagates_http_error( client: MagicPlanClient, mock_session: MagicMock ) -> None: # Arrange mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError( "500" ) # Act / Assert with pytest.raises(requests.HTTPError): client.get_plan_raw("some-id")