Files
chitai/backend/tests/conftest.py
2025-12-04 00:33:37 -05:00

258 lines
6.9 KiB
Python

from pathlib import Path
from uuid import uuid4
import pytest
from advanced_alchemy.base import UUIDAuditBase
from litestar.testing import AsyncTestClient
from sqlalchemy import text
from sqlalchemy.ext.asyncio import (
AsyncSession,
AsyncEngine,
async_sessionmaker,
create_async_engine,
)
from sqlalchemy.engine import URL
from sqlalchemy.pool import NullPool
from chitai.database import models as m
from chitai.config import settings
from collections.abc import AsyncGenerator
from litestar import Litestar
from pytest_databases.docker.postgres import PostgresService
from sqlalchemy.orm import selectinload
pytest_plugins = [
"tests.data_fixtures",
"pytest_databases.docker",
"pytest_databases.docker.postgres",
]
# Set the environment to use the testing database
# os.environ.update(
# {
# "POSTGRES_DB": "chitai_testing"
# }
# )
@pytest.fixture(autouse=True)
def _patch_settings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(settings, "book_cover_path", f"{tmp_path}/covers")
@pytest.fixture(name="engine")
async def fx_engine(
postgres_service: PostgresService,
) -> AsyncGenerator[AsyncEngine, None]:
"""PostgreSQL instance for testing"""
engine = create_async_engine(
URL(
drivername="postgresql+asyncpg",
username=postgres_service.user,
password=postgres_service.password,
host=postgres_service.host,
port=postgres_service.port,
database=postgres_service.database,
query={}, # type:ignore[arg-type]
),
echo=False,
poolclass=NullPool,
)
# Add pg_trgm extension
async with engine.begin() as conn:
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS pg_trgm"))
# Create all tables
metadata = UUIDAuditBase.registry.metadata
async with engine.begin() as conn:
await conn.run_sync(metadata.create_all)
yield engine
# Clean up
async with engine.begin() as conn:
await conn.run_sync(metadata.drop_all)
await engine.dispose()
@pytest.fixture(name="sessionmaker")
def fx_sessionmaker(engine: AsyncEngine) -> async_sessionmaker[AsyncSession]:
"""Create sessionmaker factory."""
return async_sessionmaker(bind=engine, expire_on_commit=False)
@pytest.fixture
async def session(
sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[AsyncSession, None]:
"""Create database session for tests."""
async with sessionmaker() as session:
yield session
await session.rollback()
await session.close()
@pytest.fixture
async def app() -> Litestar:
"""Create Litestar app for testing."""
from chitai.app import create_app
return create_app()
@pytest.fixture
async def client(app: Litestar) -> AsyncGenerator[AsyncTestClient, None]:
"""Create test client."""
async with AsyncTestClient(app=app) as client:
yield client
@pytest.fixture
async def authenticated_client(
client: AsyncTestClient, test_user: m.User
) -> AsyncTestClient:
"""Create authenticated test client."""
# login and set auth headers
login_response = await client.post(
"access/login", data={"email": test_user.email, "password": "password123"}
)
assert login_response.status_code == 201
result = login_response.json()
token = result["access_token"]
client.headers.update({"Authorization": f"Bearer {token}"})
return client
@pytest.fixture
async def other_authenticated_client(
app: Litestar, test_user2: m.User
) -> AsyncGenerator[AsyncTestClient, None]:
"""Create second authenticated test client as different user."""
async with AsyncTestClient(app=app) as other_client:
login_response = await other_client.post(
"access/login", data={"email": test_user2.email, "password": "password234"}
)
assert login_response.status_code == 201
result = login_response.json()
token = result["access_token"]
other_client.headers.update({"Authorization": f"Bearer {token}"})
yield other_client
# Service fixtures
from chitai import services
@pytest.fixture
async def user_service(
sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.UserService, None]:
"""Create UserService instance."""
async with sessionmaker() as session:
async with services.UserService.new(session) as service:
yield service
@pytest.fixture
async def library_service(
sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.LibraryService, None]:
"""Create LibraryService instance."""
async with sessionmaker() as session:
async with services.LibraryService.new(session) as service:
yield service
@pytest.fixture
async def books_service(
sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.BookService, None]:
"""Create BookService instance."""
async with sessionmaker() as session:
async with services.BookService.new(
session,
load=[
selectinload(m.Book.author_links).selectinload(m.BookAuthorLink.author),
selectinload(m.Book.tag_links).selectinload(m.BookTagLink.tag),
m.Book.publisher,
m.Book.files,
m.Book.identifiers,
m.Book.series,
],
) as service:
yield service
@pytest.fixture
async def bookshelf_service(
sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.ShelfService]:
"""Create ShelfService instance."""
async with sessionmaker() as session:
async with services.ShelfService.new(session) as service:
yield service
# Data fixtures
@pytest.fixture
async def test_user(session: AsyncSession) -> AsyncGenerator[m.User, None]:
"""Create a test user."""
unique_id = str(uuid4())[:8]
user = m.User(
email=f"user{unique_id}@example.com",
password="password123",
)
session.add(user)
await session.commit()
await session.refresh(user)
yield user
@pytest.fixture
async def test_user2(session: AsyncSession) -> AsyncGenerator[m.User, None]:
"""Create another test user."""
unique_id = str(uuid4())[:8]
user = m.User(
email=f"user{unique_id}@example.com",
password="password234",
)
session.add(user)
await session.commit()
await session.refresh(user)
yield user
@pytest.fixture
async def test_library(
session: AsyncSession, tmp_path: Path
) -> AsyncGenerator[m.Library, None]:
"""Create a test library."""
library = m.Library(
name="Testing Library",
slug="testing-library",
root_path=str(tmp_path),
path_template="{author_name}/{title}.{ext}",
read_only=False,
)
session.add(library)
await session.commit()
await session.refresh(library)
yield library