diff --git a/api/organisations/tasks.py b/api/organisations/tasks.py index 14f7c7331daa..0b3b9267f7c2 100644 --- a/api/organisations/tasks.py +++ b/api/organisations/tasks.py @@ -230,19 +230,27 @@ def charge_for_api_call_count_overages(): logger.info("API Usage below current API limit.") continue - if organisation.subscription.plan in {SCALE_UP, SCALE_UP_V2}: - add_100k_api_calls_scale_up( - organisation.subscription.subscription_id, - math.ceil(api_overage / 100_000), - ) - elif organisation.subscription.plan in {STARTUP, STARTUP_V2}: - add_100k_api_calls_start_up( - organisation.subscription.subscription_id, - math.ceil(api_overage / 100_000), - ) - else: + try: + if organisation.subscription.plan in {SCALE_UP, SCALE_UP_V2}: + add_100k_api_calls_scale_up( + organisation.subscription.subscription_id, + math.ceil(api_overage / 100_000), + ) + elif organisation.subscription.plan in {STARTUP, STARTUP_V2}: + add_100k_api_calls_start_up( + organisation.subscription.subscription_id, + math.ceil(api_overage / 100_000), + ) + else: + logger.error( + f"Unable to bill for API overages for plan `{organisation.subscription.plan}` " + f"for organisation {organisation.id}" + ) + continue + except Exception: logger.error( - f"Unable to bill for API overages for plan `{organisation.subscription.plan}`" + f"Unable to charge organisation {organisation.id} due to billing error", + exc_info=True, ) continue diff --git a/api/tests/unit/organisations/test_unit_organisations_tasks.py b/api/tests/unit/organisations/test_unit_organisations_tasks.py index d97261447a2f..6b0d6231c747 100644 --- a/api/tests/unit/organisations/test_unit_organisations_tasks.py +++ b/api/tests/unit/organisations/test_unit_organisations_tasks.py @@ -1189,6 +1189,124 @@ def test_charge_for_api_call_count_overages_start_up( calls_mock.assert_not_called() +@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00") +def test_charge_for_api_call_count_overages_non_standard( + organisation: Organisation, + mocker: MockerFixture, + inspecting_handler: logging.Handler, +) -> None: + # Given + now = timezone.now() + + from organisations.tasks import logger + + logger.addHandler(inspecting_handler) + + OrganisationSubscriptionInformationCache.objects.create( + organisation=organisation, + allowed_seats=10, + allowed_projects=3, + allowed_30d_api_calls=100_000, + chargebee_email="test@example.com", + current_billing_term_starts_at=now - timedelta(days=30), + current_billing_term_ends_at=now + timedelta(minutes=30), + ) + organisation.subscription.subscription_id = "fancy_sub_id23" + organisation.subscription.plan = "nonstandard-v2" + organisation.subscription.save() + OrganisationAPIUsageNotification.objects.create( + organisation=organisation, + percent_usage=100, + notified_at=now, + ) + + get_client_mock = mocker.patch("organisations.tasks.get_client") + client_mock = MagicMock() + get_client_mock.return_value = client_mock + client_mock.get_identity_flags.return_value.is_feature_enabled.return_value = True + mocker.patch("organisations.chargebee.chargebee.chargebee.Subscription.retrieve") + mock_chargebee_update = mocker.patch( + "organisations.chargebee.chargebee.chargebee.Subscription.update" + ) + + mock_api_usage = mocker.patch( + "organisations.tasks.get_current_api_usage", + ) + mock_api_usage.return_value = 202_005 + + # When + charge_for_api_call_count_overages() + + # Then + mock_chargebee_update.assert_not_called() + assert inspecting_handler.messages == [ + f"Unable to bill for API overages for plan `{organisation.subscription.plan}` " + f"for organisation {organisation.id}" + ] + + assert OrganisationAPIBilling.objects.count() == 0 + + +@pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00") +def test_charge_for_api_call_count_overages_with_exception( + organisation: Organisation, + mocker: MockerFixture, + inspecting_handler: logging.Handler, +) -> None: + # Given + now = timezone.now() + + from organisations.tasks import logger + + logger.addHandler(inspecting_handler) + + OrganisationSubscriptionInformationCache.objects.create( + organisation=organisation, + allowed_seats=10, + allowed_projects=3, + allowed_30d_api_calls=100_000, + chargebee_email="test@example.com", + current_billing_term_starts_at=now - timedelta(days=30), + current_billing_term_ends_at=now + timedelta(minutes=30), + ) + organisation.subscription.subscription_id = "fancy_sub_id23" + organisation.subscription.plan = "startup-v2" + organisation.subscription.save() + OrganisationAPIUsageNotification.objects.create( + organisation=organisation, + percent_usage=100, + notified_at=now, + ) + + get_client_mock = mocker.patch("organisations.tasks.get_client") + client_mock = MagicMock() + get_client_mock.return_value = client_mock + client_mock.get_identity_flags.return_value.is_feature_enabled.return_value = True + mocker.patch("organisations.chargebee.chargebee.chargebee.Subscription.retrieve") + mock_chargebee_update = mocker.patch( + "organisations.chargebee.chargebee.chargebee.Subscription.update" + ) + + mock_api_usage = mocker.patch( + "organisations.tasks.get_current_api_usage", + ) + mock_api_usage.return_value = 202_005 + mocker.patch( + "organisations.tasks.add_100k_api_calls_start_up", + side_effect=ValueError("An error occurred"), + ) + + # When + charge_for_api_call_count_overages() + + # Then + assert inspecting_handler.messages[0].startswith( + f"Unable to charge organisation {organisation.id} due to billing error" + ) + mock_chargebee_update.assert_not_called() + assert OrganisationAPIBilling.objects.count() == 0 + + @pytest.mark.freeze_time("2023-01-19T09:09:47.325132+00:00") def test_charge_for_api_call_count_overages_start_up_with_api_billing( organisation: Organisation,