/
/
/
1"""
2Tests for the ``start_from_beginning`` podcast playback option.
3
4The option lets an episode start at absolute position 0 while leaving the
5user's saved progress in the playlog untouched (a non-destructive counterpart
6to ``music/mark_unplayed``). See discussion music-assistant/discussions#4191.
7
8These integration tests use the ``mass`` fixture from ``tests/conftest.py``
9which creates a full MusicAssistant instance with a real SQLite database.
10"""
11
12from __future__ import annotations
13
14from uuid import uuid4
15
16from music_assistant_models.enums import MediaType
17from music_assistant_models.media_items import Podcast, PodcastEpisode, ProviderMapping
18
19from music_assistant.constants import DB_TABLE_PLAYLOG
20from music_assistant.mass import MusicAssistant
21
22
23def _provider_mapping(provider: str = "test_podcast_prov") -> set[ProviderMapping]:
24 """Create a single provider mapping with a unique item id."""
25 return {
26 ProviderMapping(
27 item_id=uuid4().hex,
28 provider_domain=provider,
29 provider_instance=provider,
30 )
31 }
32
33
34async def test_start_from_beginning_ignores_saved_resume(mass: MusicAssistant) -> None:
35 """start_from_beginning forces resume_position_ms=0 without wiping saved progress."""
36 user = await mass.webserver.auth.create_user("podcaststartfrombeginning")
37
38 podcast = Podcast(
39 item_id="show-sfb-001",
40 provider="test_podcast_prov",
41 name="Start From Beginning Show",
42 provider_mappings=_provider_mapping(),
43 )
44 episode = PodcastEpisode(
45 item_id="ep-sfb-001",
46 provider="test_podcast_prov",
47 name="Episode 1",
48 provider_mappings=_provider_mapping(),
49 position=1,
50 podcast=podcast,
51 )
52
53 # seed 120 seconds of saved progress for this episode
54 await mass.music.mark_item_played(
55 episode,
56 fully_played=False,
57 seconds_played=120,
58 user_initiated=True,
59 userid=user.user_id,
60 )
61
62 resolver = mass.player_queues._media_resolver
63
64 # sanity: without the flag, the saved 120s resume position is applied
65 normal = await resolver.get_next_podcast_episodes(None, episode, userid=user.user_id)
66 assert normal[0].resume_position_ms == 120_000
67
68 # with the flag, the episode starts at absolute 0
69 from_start = await resolver.get_next_podcast_episodes(
70 None, episode, userid=user.user_id, start_from_beginning=True
71 )
72 assert from_start[0].resume_position_ms == 0
73 assert from_start[0].fully_played is False
74
75 # the saved progress row must be left untouched (non-destructive)
76 row = await mass.music.database.get_row(
77 DB_TABLE_PLAYLOG,
78 {
79 "media_type": MediaType.PODCAST_EPISODE.value,
80 "item_id": episode.item_id,
81 "provider": episode.provider,
82 "userid": user.user_id,
83 },
84 )
85 assert row is not None, "start_from_beginning must not delete the saved progress row"
86 assert row["seconds_played"] == 120
87