/
/
/
1"""Tests for the Smart Playlist provider recommendations."""
2
3from __future__ import annotations
4
5from unittest.mock import AsyncMock, MagicMock
6
7from music_assistant_models.media_items import Playlist
8
9from music_assistant.providers.smart_playlist import SmartPlaylistProvider
10from music_assistant.providers.smart_playlist.helpers import SmartPlaylistRules
11
12
13def _make_plugin() -> tuple[SmartPlaylistProvider, MagicMock]:
14 """Create a SmartPlaylistProvider with mocked mass and empty stores."""
15 mass = MagicMock()
16 manifest = MagicMock()
17 manifest.domain = "smart_playlist"
18 config = MagicMock()
19 config.get_value.return_value = "GLOBAL"
20 plugin = SmartPlaylistProvider(mass, manifest, config, set())
21 plugin._rules_store = {}
22 plugin._names_store = {}
23 plugin._descriptions_store = {}
24 # the only backend touchpoint of the items path (playlist artwork lookup)
25 mass.music.playlists.get_library_item_by_prov_id = AsyncMock(return_value=None)
26 return plugin, mass
27
28
29async def test_get_recommendations_returns_static_row() -> None:
30 """A populated rules store yields the single static row, without items or backend I/O."""
31 plugin, mass = _make_plugin()
32 plugin._rules_store["abc"] = SmartPlaylistRules(limit=10)
33
34 result = await plugin.get_recommendations()
35
36 assert len(result) == 1
37 folder = result[0]
38 assert folder.item_id == "smart_playlists"
39 # rows must carry the instance_id: the items API resolves the provider from it
40 assert folder.provider == plugin.instance_id
41 assert folder.name == "Smart Playlists"
42 assert folder.translation_key == "smart_playlists"
43 assert len(folder.items) == 0
44 mass.music.playlists.get_library_item_by_prov_id.assert_not_awaited()
45
46
47async def test_get_recommendations_empty_store_returns_no_rows() -> None:
48 """An empty rules store yields no rows."""
49 plugin, _mass = _make_plugin()
50
51 assert await plugin.get_recommendations() == []
52
53
54async def test_get_recommendation_items_builds_playlists() -> None:
55 """The smart_playlists row builds one playlist per stored rule set."""
56 plugin, mass = _make_plugin()
57 plugin._rules_store["abc"] = SmartPlaylistRules(limit=10, is_dynamic=True)
58 plugin._rules_store["def"] = SmartPlaylistRules(limit=20, is_dynamic=False)
59 plugin._names_store["abc"] = "Playlist A"
60 plugin._names_store["def"] = "Playlist B"
61
62 result = await plugin.get_recommendation_items("smart_playlists")
63
64 assert [item.item_id for item in result] == ["abc", "def"]
65 assert all(isinstance(item, Playlist) for item in result)
66 assert [item.name for item in result] == ["Playlist A", "Playlist B"]
67 # the per-playlist artwork lookup ran for each built playlist
68 assert mass.music.playlists.get_library_item_by_prov_id.await_count == 2
69
70
71async def test_get_recommendation_items_unknown_id_returns_empty() -> None:
72 """An unknown row item_id returns empty without building any playlist."""
73 plugin, mass = _make_plugin()
74 plugin._rules_store["abc"] = SmartPlaylistRules(limit=10)
75
76 result = await plugin.get_recommendation_items("unknown_row")
77
78 assert list(result) == []
79 mass.music.playlists.get_library_item_by_prov_id.assert_not_awaited()
80