Files
chitai/backend/tests/conftest.py
patrick 2de0aac23a chore: update dependencies and ignore new warning in tests
ignore JWT related warning that was introduced when updating
dependencies related to JWT secret size (test value of "secret" is too
short).
2026-03-09 14:21:37 -04:00

278 lines
7.6 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
@pytest.fixture
async def kosync_progress_service(
sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.KosyncProgressService, None]:
"""Create KosyncProgressService instance."""
async with sessionmaker() as session:
async with services.KosyncProgressService.new(session) as service:
yield service
@pytest.fixture
async def kosync_device_service(
sessionmaker: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[services.KosyncDeviceService, None]:
"""Create KosyncDeviceService instance."""
async with sessionmaker() as session:
async with services.KosyncDeviceService.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