""" 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)