mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-20 21:18:13 +02:00
142 lines
4 KiB
Python
142 lines
4 KiB
Python
"""Shared builders for podcast unit tests.
|
|
|
|
These tests exercise the podcast domain through its public interfaces. The only
|
|
test double is a minimal stand-in for the SQLAlchemy ``AsyncSession`` — a real
|
|
system boundary — so the service's own repository and state machine run for
|
|
real. Briefs and transcripts are built with valid factories so each test states
|
|
just the fields it cares about.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.podcasts.schemas import (
|
|
DurationTarget,
|
|
PodcastSpec,
|
|
PodcastStyle,
|
|
SpeakerRole,
|
|
SpeakerSpec,
|
|
Transcript,
|
|
TranscriptTurn,
|
|
)
|
|
|
|
|
|
class FakeAsyncSession:
|
|
"""A no-op stand-in for ``AsyncSession`` at the persistence boundary.
|
|
|
|
The service flushes to assign state within a unit of work; in a unit test
|
|
there is no database, so ``add``/``flush`` simply do nothing. Behavior is
|
|
observed through the returned aggregate, never through this double.
|
|
"""
|
|
|
|
def add(self, _obj: object) -> None:
|
|
return None
|
|
|
|
async def flush(self) -> None:
|
|
return None
|
|
|
|
|
|
class FakeCeleryDbSession(FakeAsyncSession):
|
|
"""An async-context session double for Celery task bodies.
|
|
|
|
Task bodies open ``get_celery_session_maker()()`` as an async context,
|
|
``get`` the row, then ``commit``. This holds one preloaded podcast and
|
|
records whether the body committed, so tests assert on the row's final
|
|
state — not on the calls made to get there.
|
|
"""
|
|
|
|
def __init__(self, podcast: object | None = None) -> None:
|
|
self._podcast = podcast
|
|
self.committed = False
|
|
|
|
async def get(self, _model: object, _id: object) -> object | None:
|
|
return self._podcast
|
|
|
|
async def commit(self) -> None:
|
|
self.committed = True
|
|
|
|
async def __aenter__(self) -> FakeCeleryDbSession:
|
|
return self
|
|
|
|
async def __aexit__(self, *_exc: object) -> None:
|
|
return None
|
|
|
|
|
|
@pytest.fixture
|
|
def fake_session() -> FakeAsyncSession:
|
|
return FakeAsyncSession()
|
|
|
|
|
|
@pytest.fixture
|
|
def make_celery_session():
|
|
"""Factory for a Celery-style session double holding one podcast."""
|
|
|
|
def _make(podcast: object | None = None) -> FakeCeleryDbSession:
|
|
return FakeCeleryDbSession(podcast)
|
|
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def session_maker_for():
|
|
"""Build a ``get_celery_session_maker`` replacement bound to one session.
|
|
|
|
``get_celery_session_maker()()`` must yield the session, so the replacement
|
|
is a zero-arg callable returning a maker that returns the session.
|
|
"""
|
|
|
|
def _make(session: object):
|
|
return lambda: (lambda: session)
|
|
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def make_spec():
|
|
"""Factory for a valid :class:`PodcastSpec`; override only what matters."""
|
|
|
|
def _make(
|
|
*,
|
|
language: str = "en",
|
|
style: PodcastStyle = PodcastStyle.CONVERSATIONAL,
|
|
speakers: list[SpeakerSpec] | None = None,
|
|
min_minutes: int = 10,
|
|
max_minutes: int = 20,
|
|
focus: str | None = None,
|
|
) -> PodcastSpec:
|
|
if speakers is None:
|
|
speakers = [
|
|
SpeakerSpec(
|
|
slot=0, name="Host", role=SpeakerRole.HOST, voice_id="kokoro:am_adam"
|
|
),
|
|
SpeakerSpec(
|
|
slot=1,
|
|
name="Guest",
|
|
role=SpeakerRole.GUEST,
|
|
voice_id="kokoro:af_bella",
|
|
),
|
|
]
|
|
return PodcastSpec(
|
|
language=language,
|
|
style=style,
|
|
speakers=speakers,
|
|
duration=DurationTarget(min_minutes=min_minutes, max_minutes=max_minutes),
|
|
focus=focus,
|
|
)
|
|
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def make_transcript():
|
|
"""Factory for a valid :class:`Transcript`."""
|
|
|
|
def _make(turns: list[tuple[int, str]] | None = None) -> Transcript:
|
|
if turns is None:
|
|
turns = [(0, "Welcome to the show."), (1, "Glad to be here.")]
|
|
return Transcript(
|
|
turns=[TranscriptTurn(speaker=slot, text=text) for slot, text in turns]
|
|
)
|
|
|
|
return _make
|