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, PlansListResponse 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: 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 mock_session.headers.update.assert_called_once_with({"customer": CUSTOMER_ID}) # --- 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}/plans", params={"key": API_KEY} ) 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_plans_list_response( 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, PlansListResponse) assert len(result.plans) == 1 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() # --- 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/{plan_id}", params={"key": API_KEY} ) 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/{plan_id}", params={"key": API_KEY} ) 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")