diff --git a/surfsense_backend/tests/unit/podcasts/test_runtime.py b/surfsense_backend/tests/unit/podcasts/test_runtime.py new file mode 100644 index 000000000..91c6ada77 --- /dev/null +++ b/surfsense_backend/tests/unit/podcasts/test_runtime.py @@ -0,0 +1,57 @@ +"""Failure recording shared by the podcast tasks. + +When a task body raises, ``mark_failed`` is the safety net that records the +reason on the row. Its contract has two halves worth securing: a still-running +podcast is moved to FAILED with the reason, and a podcast that already reached a +terminal state is left exactly as it was rather than forced. Only the database +(a real boundary) is doubled; the lifecycle service runs for real. +""" + +from __future__ import annotations + +import pytest + +from app.podcasts.persistence import Podcast, PodcastStatus +from app.podcasts.tasks import runtime + +pytestmark = pytest.mark.unit + + +def _podcast(status: PodcastStatus) -> Podcast: + podcast = Podcast(title="Episode", search_space_id=1, status=status, spec_version=1) + podcast.id = 1 + return podcast + + +async def test_marking_failed_records_the_reason_on_a_running_podcast( + monkeypatch, session_maker_for, make_celery_session +): + podcast = _podcast(PodcastStatus.DRAFTING) + session = make_celery_session(podcast) + monkeypatch.setattr(runtime, "get_celery_session_maker", session_maker_for(session)) + + await runtime.mark_failed(1, "tts provider unavailable") + + assert podcast.status == PodcastStatus.FAILED + assert podcast.error == "tts provider unavailable" + + +async def test_marking_failed_leaves_an_already_terminal_podcast_untouched( + monkeypatch, session_maker_for, make_celery_session +): + podcast = _podcast(PodcastStatus.CANCELLED) + session = make_celery_session(podcast) + monkeypatch.setattr(runtime, "get_celery_session_maker", session_maker_for(session)) + + await runtime.mark_failed(1, "too late") + + assert podcast.status == PodcastStatus.CANCELLED + + +async def test_marking_a_missing_podcast_failed_is_a_no_op( + monkeypatch, session_maker_for, make_celery_session +): + session = make_celery_session(None) + monkeypatch.setattr(runtime, "get_celery_session_maker", session_maker_for(session)) + + await runtime.mark_failed(999, "gone") # must not raise