/
/
/
1"""Tests for the MetaDataController public API."""
2
3from __future__ import annotations
4
5from typing import TYPE_CHECKING
6
7import pytest
8from music_assistant_models.media_items import MediaItemPalette
9
10if TYPE_CHECKING:
11 from music_assistant.controllers.metadata import MetaDataController
12
13# every test here registers image ids, which are persisted to the cache database
14pytestmark = pytest.mark.usefixtures("cache_database")
15
16
17async def test_get_image_palette_resolves_registered_id(
18 metadata_controller: MetaDataController, monkeypatch: pytest.MonkeyPatch
19) -> None:
20 """A registered image id resolves to its (provider, path) and yields its palette."""
21 palette = MediaItemPalette(primary=(1, 2, 3))
22 calls: dict[str, object] = {}
23
24 async def fake_get_palette(_mass: object, path: str, provider: str) -> MediaItemPalette:
25 calls["get_palette"] = (path, provider)
26 return palette
27
28 monkeypatch.setattr("music_assistant.controllers.metadata.images.get_palette", fake_get_palette)
29
30 image_id = metadata_controller.compute_image_id("spotify", "cover.jpg")
31 assert await metadata_controller.get_image_palette(image_id) is palette
32 assert calls["get_palette"] == ("cover.jpg", "spotify")
33
34
35async def test_get_image_palette_rejects_imageproxy_url(
36 metadata_controller: MetaDataController, monkeypatch: pytest.MonkeyPatch
37) -> None:
38 """Only a bare image id is accepted; a full /imageproxy/<id> URL yields None."""
39 fetched = False
40
41 async def fake_get_palette(_mass: object, _path: str, _provider: str) -> MediaItemPalette:
42 nonlocal fetched
43 fetched = True
44 return MediaItemPalette(primary=(4, 5, 6))
45
46 monkeypatch.setattr("music_assistant.controllers.metadata.images.get_palette", fake_get_palette)
47
48 image_id = metadata_controller.compute_image_id("filesystem", "/x.jpg")
49 url = f"http://mass.local/imageproxy/{image_id}?size=256"
50 assert await metadata_controller.get_image_palette(url) is None
51 assert fetched is False
52
53
54async def test_get_image_palette_rejects_unregistered_input(
55 metadata_controller: MetaDataController, monkeypatch: pytest.MonkeyPatch
56) -> None:
57 """Unknown ids and arbitrary URLs yield None and never trigger a fetch (no SSRF)."""
58 fetched = False
59
60 async def fake_get_palette(_mass: object, _path: str, _provider: str) -> MediaItemPalette:
61 nonlocal fetched
62 fetched = True
63 return MediaItemPalette(primary=(0, 0, 0))
64
65 monkeypatch.setattr("music_assistant.controllers.metadata.images.get_palette", fake_get_palette)
66
67 # an id that was never registered
68 assert await metadata_controller.get_image_palette("0" * 64) is None
69 # an arbitrary URL must not be resolved or fetched
70 assert await metadata_controller.get_image_palette("http://169.254.169.254/latest") is None
71 assert fetched is False
72
73
74async def test_get_image_palette_returns_none_for_unreadable_image(
75 metadata_controller: MetaDataController, monkeypatch: pytest.MonkeyPatch
76) -> None:
77 """A registered image whose data can't be read yields None instead of raising."""
78
79 async def boom(_mass: object, path: str, _provider: str) -> MediaItemPalette:
80 raise FileNotFoundError(path)
81
82 monkeypatch.setattr("music_assistant.controllers.metadata.images.get_palette", boom)
83 image_id = metadata_controller.compute_image_id("filesystem", "missing.jpg")
84 assert await metadata_controller.get_image_palette(image_id) is None
85