SurfSense/surfsense_backend/tests/integration/podcasts/test_transcript_gate.py
CREDO23 c84525897b test(podcasts): relocate stateful tests to integration
Move the lifecycle service, Celery task bodies, and mark_failed coverage out of
DB-faking unit tests and into integration tests against a real Postgres, faking
only true externals (broker, object store, TTS, ffmpeg, billing, LLM). Add HTTP
slices for cancel, voices, scoping, and public-chat streaming. The unit tier is
now fake-free pure logic with no session doubles.
2026-06-11 06:27:00 +02:00

81 lines
2.7 KiB
Python

"""The transcript go/no-go gate: approve to render, or regenerate to redraft.
From ``awaiting_review`` the user either approves (start rendering) or regenerates
(redraft). These pin the resulting state, the Celery task each enqueues, and the
HTTP codes for acting from the wrong state (409) or without a transcript (422).
"""
from __future__ import annotations
import pytest
from app.podcasts.persistence import Podcast, PodcastStatus
pytestmark = pytest.mark.integration
BASE = "/api/v1/podcasts"
async def test_approve_transcript_starts_rendering_and_enqueues_render(
client, db_search_space, make_podcast, captured_tasks
):
podcast = await make_podcast(
search_space_id=db_search_space.id, status=PodcastStatus.AWAITING_REVIEW
)
resp = await client.post(f"{BASE}/{podcast.id}/transcript/approve")
assert resp.status_code == 200
assert resp.json()["status"] == "rendering"
assert captured_tasks.render == [((podcast.id,), {})]
assert captured_tasks.draft == []
async def test_regenerate_returns_to_drafting_and_enqueues_draft(
client, db_search_space, make_podcast, captured_tasks
):
podcast = await make_podcast(
search_space_id=db_search_space.id, status=PodcastStatus.AWAITING_REVIEW
)
resp = await client.post(f"{BASE}/{podcast.id}/transcript/regenerate")
assert resp.status_code == 200
assert resp.json()["status"] == "drafting"
assert captured_tasks.draft == [((podcast.id, db_search_space.id), {})]
assert captured_tasks.render == []
async def test_approve_transcript_from_terminal_state_is_rejected(
client, db_search_space, make_podcast, captured_tasks
):
# A ready podcast still has its transcript, so the precondition passes and
# the disallowed terminal->rendering transition is what surfaces (409).
podcast = await make_podcast(
search_space_id=db_search_space.id, status=PodcastStatus.READY
)
resp = await client.post(f"{BASE}/{podcast.id}/transcript/approve")
assert resp.status_code == 409
assert captured_tasks.render == []
async def test_approve_without_transcript_is_unprocessable(
client, db_session, db_search_space, captured_tasks
):
# An anomalous awaiting_review row with no transcript exercises the route's
# precondition->422 mapping (the service refuses to render without one).
podcast = Podcast(
title="No transcript",
search_space_id=db_search_space.id,
status=PodcastStatus.AWAITING_REVIEW,
spec_version=1,
)
db_session.add(podcast)
await db_session.flush()
resp = await client.post(f"{BASE}/{podcast.id}/transcript/approve")
assert resp.status_code == 422
assert captured_tasks.render == []