mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-30 03:00:41 -04:00
fix(backend): guard modify_stripe_subscription_for_tier against orphaned customers
Add early return when user has no stripe_customer_id to prevent creating an orphaned Stripe customer if a subsequent Subscription.list call fails. Follows the same pattern as cancel_stripe_subscription and get_proration_credit_cents. Update tests to mock get_user_by_id and add a test for the no-customer-id path.
This commit is contained in:
@@ -1437,7 +1437,15 @@ async def modify_stripe_subscription_for_tier(
|
||||
if not price_id:
|
||||
raise ValueError(f"No Stripe price ID configured for tier {tier}")
|
||||
|
||||
customer_id = await get_stripe_customer_id(user_id)
|
||||
# Guard: only proceed if the user already has a Stripe customer ID. Calling
|
||||
# get_stripe_customer_id for a user with no Stripe record (e.g. admin-granted tier)
|
||||
# would create an orphaned customer object if the subsequent Subscription.list call
|
||||
# fails. Return False early so the API layer falls back to Checkout instead.
|
||||
user = await get_user_by_id(user_id)
|
||||
if not user.stripe_customer_id:
|
||||
return False
|
||||
|
||||
customer_id = user.stripe_customer_id
|
||||
for status in ("active", "trialing"):
|
||||
subscriptions = await run_in_threadpool(
|
||||
stripe.Subscription.list, customer=customer_id, status=status, limit=1
|
||||
|
||||
@@ -1106,6 +1106,9 @@ async def test_modify_stripe_subscription_for_tier_modifies_existing_sub():
|
||||
mock_list = MagicMock()
|
||||
mock_list.data = [mock_sub]
|
||||
|
||||
mock_user = MagicMock(spec=User)
|
||||
mock_user.stripe_customer_id = "cus_abc"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"backend.data.credit.get_subscription_price_id",
|
||||
@@ -1113,9 +1116,9 @@ async def test_modify_stripe_subscription_for_tier_modifies_existing_sub():
|
||||
return_value="price_pro_monthly",
|
||||
),
|
||||
patch(
|
||||
"backend.data.credit.get_stripe_customer_id",
|
||||
"backend.data.credit.get_user_by_id",
|
||||
new_callable=AsyncMock,
|
||||
return_value="cus_abc",
|
||||
return_value=mock_user,
|
||||
),
|
||||
patch(
|
||||
"backend.data.credit.stripe.Subscription.list",
|
||||
@@ -1138,10 +1141,15 @@ async def test_modify_stripe_subscription_for_tier_modifies_existing_sub():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modify_stripe_subscription_for_tier_returns_false_when_no_sub():
|
||||
"""modify_stripe_subscription_for_tier returns False when no active subscription exists."""
|
||||
mock_list = MagicMock()
|
||||
mock_list.data = []
|
||||
async def test_modify_stripe_subscription_for_tier_returns_false_when_no_customer_id():
|
||||
"""modify_stripe_subscription_for_tier returns False when user has no Stripe customer ID.
|
||||
|
||||
Admin-granted paid tiers have no Stripe customer record. Calling
|
||||
get_stripe_customer_id would create an orphaned customer if a subsequent API call
|
||||
fails, so the function returns False early and the API layer falls back to Checkout.
|
||||
"""
|
||||
mock_user = MagicMock(spec=User)
|
||||
mock_user.stripe_customer_id = None
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -1150,9 +1158,37 @@ async def test_modify_stripe_subscription_for_tier_returns_false_when_no_sub():
|
||||
return_value="price_pro_monthly",
|
||||
),
|
||||
patch(
|
||||
"backend.data.credit.get_stripe_customer_id",
|
||||
"backend.data.credit.get_user_by_id",
|
||||
new_callable=AsyncMock,
|
||||
return_value="cus_abc",
|
||||
return_value=mock_user,
|
||||
),
|
||||
):
|
||||
result = await modify_stripe_subscription_for_tier(
|
||||
"user-1", SubscriptionTier.PRO
|
||||
)
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modify_stripe_subscription_for_tier_returns_false_when_no_sub():
|
||||
"""modify_stripe_subscription_for_tier returns False when no active subscription exists."""
|
||||
mock_list = MagicMock()
|
||||
mock_list.data = []
|
||||
|
||||
mock_user = MagicMock(spec=User)
|
||||
mock_user.stripe_customer_id = "cus_abc"
|
||||
|
||||
with (
|
||||
patch(
|
||||
"backend.data.credit.get_subscription_price_id",
|
||||
new_callable=AsyncMock,
|
||||
return_value="price_pro_monthly",
|
||||
),
|
||||
patch(
|
||||
"backend.data.credit.get_user_by_id",
|
||||
new_callable=AsyncMock,
|
||||
return_value=mock_user,
|
||||
),
|
||||
patch(
|
||||
"backend.data.credit.stripe.Subscription.list",
|
||||
|
||||
Reference in New Issue
Block a user