Spaces:
Running
Running
| """Author RAG β Pytest Shared Fixtures. | |
| Provides async DB, fakeredis, mocked OpenAI, and mocked Stripe | |
| for all test modules. Zero real network calls in tests. | |
| """ | |
| import pytest | |
| import pytest_asyncio | |
| from httpx import AsyncClient, ASGITransport | |
| from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker | |
| from unittest.mock import AsyncMock, MagicMock, patch | |
| from app.models.base import Base | |
| from app.main import create_app | |
| from app.dependencies import get_db, get_redis | |
| # ββ In-Memory SQLite DB βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def db_engine(): | |
| engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False) | |
| async with engine.begin() as conn: | |
| await conn.run_sync(Base.metadata.create_all) | |
| yield engine | |
| async with engine.begin() as conn: | |
| await conn.run_sync(Base.metadata.drop_all) | |
| await engine.dispose() | |
| async def db_session(db_engine): | |
| factory = async_sessionmaker(db_engine, expire_on_commit=False) | |
| async with factory() as session: | |
| yield session | |
| # ββ Fake Redis ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def fake_redis(): | |
| try: | |
| import fakeredis.aioredis as fakeredis | |
| return fakeredis.FakeRedis(decode_responses=True) | |
| except ImportError: | |
| redis = MagicMock() | |
| redis.get = AsyncMock(return_value=None) | |
| redis.set = AsyncMock(return_value=True) | |
| redis.setex = AsyncMock(return_value=True) | |
| redis.delete = AsyncMock(return_value=1) | |
| redis.exists = AsyncMock(return_value=0) | |
| redis.incr = AsyncMock(return_value=1) | |
| redis.expire = AsyncMock(return_value=True) | |
| return redis | |
| # ββ FastAPI Test Client βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def client(db_session, fake_redis): | |
| app = create_app() | |
| async def override_db(): | |
| yield db_session | |
| async def override_redis(): | |
| return fake_redis | |
| app.dependency_overrides[get_db] = override_db | |
| app.dependency_overrides[get_redis] = override_redis | |
| async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: | |
| yield c | |
| app.dependency_overrides.clear() | |
| # ββ Mock OpenAI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def mock_openai(): | |
| mock_response = MagicMock() | |
| mock_response.choices = [MagicMock()] | |
| mock_response.choices[0].message.content = "This is a mock AI response." | |
| mock_response.usage.total_tokens = 50 | |
| with patch("openai.AsyncOpenAI") as mock_client: | |
| instance = mock_client.return_value | |
| instance.chat.completions.create = AsyncMock(return_value=mock_response) | |
| instance.embeddings.create = AsyncMock(return_value=MagicMock( | |
| data=[MagicMock(embedding=[0.1] * 1536)] | |
| )) | |
| yield mock_client | |
| # ββ Mock Stripe βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def mock_stripe(): | |
| mock_session = MagicMock() | |
| mock_session.id = "cs_test_mock_session" | |
| mock_session.url = "https://checkout.stripe.com/test" | |
| mock_session.payment_status = "paid" | |
| mock_session.customer_details = MagicMock(email="test@example.com", name="Test Author") | |
| mock_session.customer = "cus_test_123" | |
| mock_session.subscription = "sub_test_123" | |
| mock_session.metadata = {"plan_id": "starter"} | |
| with patch("stripe.checkout.Session.create", return_value=mock_session), \ | |
| patch("stripe.checkout.Session.retrieve", return_value=mock_session), \ | |
| patch("stripe.Webhook.construct_event") as mock_webhook, \ | |
| patch("stripe.Subscription.retrieve"), \ | |
| patch("stripe.billing_portal.Session.create"): | |
| mock_webhook.return_value = MagicMock(id="evt_test_123", type="checkout.session.completed") | |
| yield {"session": mock_session, "webhook": mock_webhook} | |
| # ββ Fixtures ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def test_author(db_session): | |
| from datetime import datetime, timedelta, timezone | |
| from app.models.user import User | |
| from app.models.client_access import ClientAccess | |
| from app.models.base import generate_uuid | |
| import bcrypt | |
| user = User( | |
| id=generate_uuid(), email="author@test.com", | |
| password_hash=bcrypt.hashpw(b"TestPass123!", bcrypt.gensalt()).decode(), | |
| role="author", full_name="Test Author", is_active=True, stripe_plan_id="starter", | |
| ) | |
| db_session.add(user) | |
| now = datetime.now(timezone.utc) | |
| access = ClientAccess( | |
| id=generate_uuid(), author_id=user.id, granted_by=None, | |
| plan="starter", plan_id="starter", granted_at=now, | |
| expires_at=now + timedelta(days=365), token_hash="test_token_hash", | |
| token_budget=500_000, tokens_used=0, auto_renew=True, | |
| ) | |
| db_session.add(access) | |
| await db_session.commit() | |
| return user | |
| async def test_pricing_plan(db_session): | |
| import json | |
| from app.models.pricing_plan import PricingPlan | |
| plan = PricingPlan( | |
| id="starter", display_name="Starter", | |
| monthly_price_usd=49.0, annual_price_usd=39.0, | |
| stripe_price_id_monthly="price_test_monthly", | |
| stripe_price_id_annual="price_test_annual", | |
| max_books=5, max_chats_per_month=2000, max_token_budget=500_000, max_embeds=1, | |
| features_json=json.dumps(["5 books", "2,000 chats/month", "Email support"]), | |
| is_active=True, is_popular=False, sort_order=1, | |
| ) | |
| db_session.add(plan) | |
| await db_session.commit() | |
| return plan | |