84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
"""
|
|
Stripe webhook handling is a 100%-coverage path per CLAUDE.md §18.
|
|
These tests exercise the pure data-transformation logic in
|
|
billing_service (no real Stripe API calls are made).
|
|
"""
|
|
from services import billing_service
|
|
from repositories.tenant_repository import tenant_repository
|
|
|
|
|
|
def test_checkout_completed_with_upgrade_metadata_updates_tenant():
|
|
tenant = tenant_repository.create({
|
|
'name': 'G Adventures', 'domain': 'gadventures.com', 'tier': 'analyse',
|
|
'status': 'active', 'max_seats': 5,
|
|
})
|
|
|
|
billing_service.handle_checkout_completed({
|
|
'metadata': {'tenant_id': tenant['id'], 'target_tier': 'discover'},
|
|
'subscription': 'sub_123',
|
|
})
|
|
|
|
updated = tenant_repository.get_by_id(tenant['id'])
|
|
assert updated['tier'] == 'discover'
|
|
assert updated['max_seats'] == 8
|
|
assert updated['stripe_subscription_id'] == 'sub_123'
|
|
assert updated['status'] == 'trial'
|
|
assert updated['trial_ends_at'] is not None
|
|
|
|
|
|
def test_checkout_completed_without_metadata_is_ignored_not_raised():
|
|
# New-signup checkout sessions (no upgrade metadata) are out of
|
|
# scope for this handler — manual onboarding handles new tenants.
|
|
# Must not raise.
|
|
billing_service.handle_checkout_completed({'metadata': {}})
|
|
|
|
|
|
def test_subscription_updated_changes_tier_when_present_in_metadata():
|
|
tenant = tenant_repository.create({
|
|
'name': 'G Adventures', 'domain': 'gadventures.com', 'tier': 'analyse',
|
|
'status': 'active', 'max_seats': 5,
|
|
})
|
|
|
|
billing_service.handle_subscription_updated({'metadata': {'tier': 'intelligence'}}, tenant['id'])
|
|
|
|
updated = tenant_repository.get_by_id(tenant['id'])
|
|
assert updated['tier'] == 'intelligence'
|
|
assert updated['max_seats'] == 10
|
|
|
|
|
|
def test_subscription_updated_no_op_without_tier_metadata():
|
|
tenant = tenant_repository.create({
|
|
'name': 'G Adventures', 'domain': 'gadventures.com', 'tier': 'analyse',
|
|
'status': 'active', 'max_seats': 5,
|
|
})
|
|
|
|
billing_service.handle_subscription_updated({'metadata': {}}, tenant['id'])
|
|
|
|
updated = tenant_repository.get_by_id(tenant['id'])
|
|
assert updated['tier'] == 'analyse'
|
|
|
|
|
|
def test_subscription_deleted_marks_tenant_inactive():
|
|
tenant = tenant_repository.create({
|
|
'name': 'G Adventures', 'domain': 'gadventures.com', 'tier': 'analyse',
|
|
'status': 'active', 'max_seats': 5,
|
|
})
|
|
|
|
billing_service.handle_subscription_deleted(tenant['id'])
|
|
|
|
assert tenant_repository.get_by_id(tenant['id'])['status'] == 'inactive'
|
|
|
|
|
|
def test_upgrade_checkout_rejects_unconfigured_tier(monkeypatch):
|
|
tenant = tenant_repository.create({
|
|
'name': 'G Adventures', 'domain': 'gadventures.com', 'tier': 'analyse',
|
|
'status': 'active', 'max_seats': 5,
|
|
})
|
|
monkeypatch.delenv('STRIPE_PRICE_DISCOVER', raising=False)
|
|
|
|
try:
|
|
billing_service.create_upgrade_checkout(tenant['id'], 'discover')
|
|
assert False, 'expected ValueError'
|
|
except ValueError as e:
|
|
assert 'No Stripe price configured' in str(e)
|