Arag / tests /conftest.py
AuthorBot
Add enterprise growth APIs, billing, admin UI, and test suite.
12930b5
Raw
History Blame Contribute Delete
6.52 kB
"""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 ───────────────────────────────────────────────────────
@pytest_asyncio.fixture(scope="function")
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()
@pytest_asyncio.fixture(scope="function")
async def db_session(db_engine):
factory = async_sessionmaker(db_engine, expire_on_commit=False)
async with factory() as session:
yield session
# ── Fake Redis ────────────────────────────────────────────────────────────────
@pytest.fixture
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 ───────────────────────────────────────────────────────
@pytest_asyncio.fixture(scope="function")
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 ───────────────────────────────────────────────────────────────
@pytest.fixture(autouse=True)
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 ───────────────────────────────────────────────────────────────
@pytest.fixture
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 ──────────────────────────────────────────────────────────────────
@pytest_asyncio.fixture
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
@pytest_asyncio.fixture
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