diff --git a/api/organisations/chargebee/chargebee.py b/api/organisations/chargebee/chargebee.py index 919a566a4a24..492fed2bd09b 100644 --- a/api/organisations/chargebee/chargebee.py +++ b/api/organisations/chargebee/chargebee.py @@ -190,8 +190,7 @@ def _convert_chargebee_subscription_to_dictionary( ) -> dict: chargebee_subscription_dict = vars(chargebee_subscription) # convert the addons into a list of dictionaries since vars don't do it recursively - chargebee_subscription_dict["addons"] = [ - vars(addon) for addon in chargebee_subscription.addons - ] + addons = chargebee_subscription.addons or [] + chargebee_subscription_dict["addons"] = [vars(addon) for addon in addons] return chargebee_subscription_dict diff --git a/api/tests/unit/organisations/chargebee/conftest.py b/api/tests/unit/organisations/chargebee/conftest.py index 46f14ba09c4c..47b5ecf8fa59 100644 --- a/api/tests/unit/organisations/chargebee/conftest.py +++ b/api/tests/unit/organisations/chargebee/conftest.py @@ -1,3 +1,5 @@ +import typing + import pytest from pytest_mock import MockerFixture from tests.unit.organisations.chargebee.test_unit_chargebee_chargebee import ( @@ -7,6 +9,14 @@ from organisations.chargebee.metadata import ChargebeeObjMetadata +ChargebeeCacheMocker = typing.Callable[ + [ + typing.Optional[dict[str, ChargebeeObjMetadata]], + typing.Optional[dict[str, ChargebeeObjMetadata]], + ], + None, +] + @pytest.fixture def chargebee_object_metadata(): @@ -17,6 +27,36 @@ def chargebee_object_metadata(): def mock_subscription_response( mocker: MockerFixture, chargebee_object_metadata: ChargebeeObjMetadata, + chargebee_cache_mocker: ChargebeeCacheMocker, +) -> MockChargeBeeSubscriptionResponse: + # Given + plan_id = "plan-id" + subscription_id = "subscription-id" + customer_email = "test@example.com" + + # Let's create a (mocked) subscription object + mock_subscription_response = MockChargeBeeSubscriptionResponse( + subscription_id=subscription_id, + plan_id=plan_id, + customer_email=customer_email, + addons=None, + ) + mocked_chargebee = mocker.patch("organisations.chargebee.chargebee.chargebee") + + # tie that subscription object to the mocked chargebee object + mocked_chargebee.Subscription.retrieve.return_value = mock_subscription_response + + # now, let's mock chargebee cache object + chargebee_cache_mocker({plan_id: chargebee_object_metadata}, None) + + return mock_subscription_response + + +@pytest.fixture +def mock_subscription_response_with_addons( + mocker: MockerFixture, + chargebee_object_metadata: ChargebeeObjMetadata, + chargebee_cache_mocker: ChargebeeCacheMocker, ) -> MockChargeBeeSubscriptionResponse: # Given plan_id = "plan-id" @@ -37,10 +77,25 @@ def mock_subscription_response( mocked_chargebee.Subscription.retrieve.return_value = mock_subscription_response # now, let's mock chargebee cache object - mocked_chargebee_cache = mocker.patch( - "organisations.chargebee.chargebee.ChargebeeCache", autospec=True + chargebee_cache_mocker( + {plan_id: chargebee_object_metadata}, {addon_id: chargebee_object_metadata} ) - mocked_chargebee_cache.return_value.plans = {plan_id: chargebee_object_metadata} - mocked_chargebee_cache.return_value.addons = {addon_id: chargebee_object_metadata} return mock_subscription_response + + +@pytest.fixture() +def chargebee_cache_mocker( + mocker: MockerFixture, +) -> ChargebeeCacheMocker: + def mock_chargebee_cache( + plans_data: dict[str, ChargebeeObjMetadata] = None, + addons_data: dict[str, ChargebeeObjMetadata] = None, + ) -> None: + mocked_chargebee_cache = mocker.patch( + "organisations.chargebee.chargebee.ChargebeeCache", autospec=True + ) + mocked_chargebee_cache.return_value.plans = plans_data or {} + mocked_chargebee_cache.return_value.addons = addons_data or {} + + return mock_chargebee_cache diff --git a/api/tests/unit/organisations/chargebee/test_unit_chargebee_chargebee.py b/api/tests/unit/organisations/chargebee/test_unit_chargebee_chargebee.py index 2e4d7ab22a0c..6ea625760ff7 100644 --- a/api/tests/unit/organisations/chargebee/test_unit_chargebee_chargebee.py +++ b/api/tests/unit/organisations/chargebee/test_unit_chargebee_chargebee.py @@ -263,7 +263,8 @@ def test_get_hosted_page_url_for_subscription_upgrade(self): def test_extract_subscription_metadata( - mock_subscription_response, chargebee_object_metadata: ChargebeeObjMetadata + mock_subscription_response_with_addons: MockChargeBeeSubscriptionResponse, + chargebee_object_metadata: ChargebeeObjMetadata, ): # Given status = "status" @@ -300,7 +301,8 @@ def test_extract_subscription_metadata( def test_extract_subscription_metadata_when_addon_list_is_empty( - mock_subscription_response, chargebee_object_metadata: ChargebeeObjMetadata + mock_subscription_response_with_addons: MockChargeBeeSubscriptionResponse, + chargebee_object_metadata: ChargebeeObjMetadata, ): # Given status = "status" @@ -329,16 +331,19 @@ def test_extract_subscription_metadata_when_addon_list_is_empty( def test_get_subscription_metadata_from_id( - mock_subscription_response, chargebee_object_metadata: ChargebeeObjMetadata + mock_subscription_response_with_addons: MockChargeBeeSubscriptionResponse, + chargebee_object_metadata: ChargebeeObjMetadata, ): # Given customer_email = "test@example.com" - subscription_id = mock_subscription_response.subscription.id + subscription_id = mock_subscription_response_with_addons.subscription.id # When subscription_metadata = get_subscription_metadata_from_id(subscription_id) # Then + # Values here are multiplied by 2 because the both the plan and the addon included in + # the mock_subscription_response_with_addons fixture contain the same values. assert subscription_metadata.seats == chargebee_object_metadata.seats * 2 assert subscription_metadata.api_calls == chargebee_object_metadata.api_calls * 2 assert subscription_metadata.projects == chargebee_object_metadata.projects * 2 @@ -425,6 +430,23 @@ def test_get_subscription_metadata_from_id_returns_none_for_invalid_subscription assert subscription_metadata is None +def test_get_subscription_metadata_from_id_returns_valid_metadata_if_addons_is_none( + mock_subscription_response: MockChargeBeeSubscriptionResponse, + chargebee_object_metadata: ChargebeeObjMetadata, +) -> None: + # Given + mock_subscription_response.addons = None + subscription_id = mock_subscription_response.subscription.id + + # When + subscription_metadata = get_subscription_metadata_from_id(subscription_id) + + # Then + assert subscription_metadata.seats == chargebee_object_metadata.seats + assert subscription_metadata.api_calls == chargebee_object_metadata.api_calls + assert subscription_metadata.projects == chargebee_object_metadata.projects + + def test_add_single_seat_with_existing_addon(mocker): # Given plan_id = "plan-id"