Skip to content

Commit

Permalink
fix: Catch API billing errors (#4514)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachaysan committed Aug 19, 2024
1 parent d273679 commit 33074f3
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 12 deletions.
32 changes: 20 additions & 12 deletions api/organisations/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
118 changes: 118 additions & 0 deletions api/tests/unit/organisations/test_unit_organisations_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 33074f3

Please sign in to comment.