/
/
/
1"""
2Tests that marking an item unplayed only undoes a play that was actually counted.
3
4``mark_item_played`` raises ``play_count`` for a completed play only, so removing a
5playlog row that was still in progress must leave the count alone.
6"""
7
8from __future__ import annotations
9
10from music_assistant_models.enums import MediaType
11from music_assistant_models.media_items import Audiobook, ItemMapping, ProviderMapping
12
13from music_assistant.constants import DB_TABLE_PLAYLOG
14from music_assistant.mass import MusicAssistant
15
16AUDIOBOOK_PROVIDER = "filesystem_local--AbCd"
17AUDIOBOOK_ID = "book-001"
18AUDIOBOOK_NAME = "A Library Audiobook"
19
20
21async def _add_library_audiobook(mass: MusicAssistant, play_count: int) -> str:
22 """
23 Add the audiobook under test to the library and return its library item id.
24
25 :param mass: The MusicAssistant instance to seed.
26 :param play_count: The play count to store for the library item.
27 """
28 db_item = await mass.music.audiobooks.add_item_to_library(
29 Audiobook(
30 item_id=AUDIOBOOK_ID,
31 provider=AUDIOBOOK_PROVIDER,
32 name=AUDIOBOOK_NAME,
33 provider_mappings={
34 ProviderMapping(
35 item_id=AUDIOBOOK_ID,
36 provider_domain="filesystem_local",
37 provider_instance=AUDIOBOOK_PROVIDER,
38 )
39 },
40 )
41 )
42 await mass.music.database.execute(
43 f"UPDATE {mass.music.audiobooks.db_table} SET play_count = {play_count} "
44 f"WHERE item_id = {db_item.item_id}"
45 )
46 await mass.music.database.commit()
47 return db_item.item_id
48
49
50async def _add_playlog_row(mass: MusicAssistant, userid: str, *, fully_played: bool) -> None:
51 """
52 Seed the playlog row for the audiobook under test.
53
54 :param mass: The MusicAssistant instance to seed.
55 :param userid: The user the row belongs to.
56 :param fully_played: Whether the row records a completed play.
57 """
58 await mass.music.database.insert(
59 DB_TABLE_PLAYLOG,
60 {
61 "item_id": AUDIOBOOK_ID,
62 "provider": AUDIOBOOK_PROVIDER,
63 "media_type": MediaType.AUDIOBOOK.value,
64 "name": AUDIOBOOK_NAME,
65 "userid": userid,
66 "seconds_played": 120,
67 "fully_played": fully_played,
68 "timestamp": 1000,
69 },
70 allow_replace=True,
71 )
72
73
74async def _play_count(mass: MusicAssistant, db_item_id: str) -> int:
75 """
76 Return the stored play count of the audiobook under test.
77
78 :param mass: The MusicAssistant instance to read from.
79 :param db_item_id: The library item id of the audiobook.
80 """
81 row = await mass.music.database.get_row(
82 mass.music.audiobooks.db_table, {"item_id": int(db_item_id)}
83 )
84 assert row is not None
85 return int(row["play_count"])
86
87
88def _reference() -> ItemMapping:
89 """Return the audiobook reference as a Discover row hands it out."""
90 return ItemMapping(
91 item_id=AUDIOBOOK_ID,
92 provider=AUDIOBOOK_PROVIDER,
93 name=AUDIOBOOK_NAME,
94 media_type=MediaType.AUDIOBOOK,
95 )
96
97
98async def test_marking_an_in_progress_audiobook_unplayed_keeps_the_play_count(
99 mass: MusicAssistant,
100) -> None:
101 """An unfinished play was never counted, so removing it must not discount one."""
102 user = await mass.webserver.auth.create_user("inprogressbook")
103 db_item_id = await _add_library_audiobook(mass, play_count=0)
104 await _add_playlog_row(mass, user.user_id, fully_played=False)
105
106 await mass.music.mark_item_unplayed(_reference(), userid=user.user_id)
107
108 assert await _play_count(mass, db_item_id) == 0
109
110
111async def test_marking_a_finished_audiobook_unplayed_discounts_the_play(
112 mass: MusicAssistant,
113) -> None:
114 """A completed play was counted, so removing it discounts one again."""
115 user = await mass.webserver.auth.create_user("finishedbook")
116 db_item_id = await _add_library_audiobook(mass, play_count=1)
117 await _add_playlog_row(mass, user.user_id, fully_played=True)
118
119 await mass.music.mark_item_unplayed(_reference(), userid=user.user_id)
120
121 assert await _play_count(mass, db_item_id) == 0
122
123
124async def test_play_count_is_never_driven_below_zero(mass: MusicAssistant) -> None:
125 """A completed row against an uncounted library item still leaves the count at zero."""
126 user = await mass.webserver.auth.create_user("uncountedbook")
127 db_item_id = await _add_library_audiobook(mass, play_count=0)
128 await _add_playlog_row(mass, user.user_id, fully_played=True)
129
130 await mass.music.mark_item_unplayed(_reference(), userid=user.user_id)
131
132 assert await _play_count(mass, db_item_id) == 0
133