"""Unit tests: BillingService — activation, payment events, plan changes.""" import pytest from unittest.mock import AsyncMock, MagicMock, patch from datetime import datetime, timezone @pytest.mark.asyncio async def test_activate_unpaid_session_raises(db_session): from app.services.billing_service import BillingService svc = BillingService(db_session) mock_session = MagicMock() mock_session.payment_status = "unpaid" with patch("app.services.billing_service.get_checkout_session", new_callable=AsyncMock, return_value=mock_session): with pytest.raises(ValueError, match="Payment not completed"): await svc.activate_from_session("cs_fake", "hash123") @pytest.mark.asyncio async def test_payment_succeeded_resets_tokens(db_session, test_author): from app.services.billing_service import BillingService from app.models.client_access import ClientAccess from sqlalchemy import select # Set tokens_used to non-zero result = await db_session.execute( select(ClientAccess).where(ClientAccess.author_id == test_author.id) ) access = result.scalar_one_or_none() if access: access.tokens_used = 250_000 access.stripe_subscription_id = "sub_test_reset" await db_session.commit() svc = BillingService(db_session) await svc.handle_payment_succeeded("sub_test_reset") await db_session.refresh(access) assert access.tokens_used == 0 @pytest.mark.asyncio async def test_payment_failed_sets_grace_period(db_session, test_author): from app.services.billing_service import BillingService from app.models.user import User from sqlalchemy import select test_author.stripe_subscription_id = "sub_grace_test" await db_session.commit() svc = BillingService(db_session) await svc.handle_payment_failed("sub_grace_test", grace_days=7) result = await db_session.execute( select(User).where(User.id == test_author.id) ) user = result.scalar_one_or_none() if user: assert user.payment_grace_until is not None assert user.payment_grace_until > datetime.now(timezone.utc) @pytest.mark.asyncio async def test_subscription_cancelled_revokes_access(db_session, test_author): from app.services.billing_service import BillingService from app.models.client_access import ClientAccess from sqlalchemy import select result = await db_session.execute( select(ClientAccess).where(ClientAccess.author_id == test_author.id) ) access = result.scalar_one_or_none() if access: access.stripe_subscription_id = "sub_cancel_test" await db_session.commit() svc = BillingService(db_session) await svc.handle_subscription_cancelled("sub_cancel_test") await db_session.refresh(access) assert access.is_revoked is True