/
/
/
1"""Tests for the strings.json localization contract (spec 0003)."""
2
3from __future__ import annotations
4
5import json
6from typing import TYPE_CHECKING, cast
7from unittest import mock
8
9from music_assistant.providers.yandex_music.constants import (
10 QUALITY_BALANCED,
11 QUALITY_EFFICIENT,
12 QUALITY_HIGH,
13 QUALITY_SUPERB,
14)
15from music_assistant.providers.yandex_music.provider import YandexMusicProvider
16
17from .conftest import provider_dir
18
19if TYPE_CHECKING:
20 from music_assistant_models.config_entries import ConfigEntry
21
22_PROVIDER_DIR = provider_dir()
23
24# The auth status label is dynamic (three states); it keeps an English
25# fallback in code and localizes via per-state translation keys. The
26# unofficial-provider note is a shared entry owned by the MA server and
27# authored in its common strings, not per provider.
28_DYNAMIC_LABEL_KEYS = {"label_text", "unofficial_provider_note"}
29
30
31def _load_strings() -> dict[str, dict[str, object]]:
32 """Load the provider's strings.json authoring file."""
33 data = json.loads((_PROVIDER_DIR / "strings.json").read_text(encoding="utf-8"))
34 assert isinstance(data, dict)
35 return data
36
37
38async def _get_entries() -> tuple[ConfigEntry, ...]:
39 """Collect the provider option config entries (auth now lives in the setup flow)."""
40 provider = mock.MagicMock(spec=YandexMusicProvider)
41 provider.get_config_value = mock.MagicMock(
42 side_effect=lambda _key, default=None, **_kw: default
43 )
44 return await YandexMusicProvider.get_config_entries(provider)
45
46
47async def test_strings_json_covers_config_entries() -> None:
48 """Every config entry key is authored in strings.json config_entries."""
49 strings = _load_strings()
50 authored = strings["config_entries"]
51 entries = await _get_entries()
52 missing = [e.key for e in entries if e.key not in _DYNAMIC_LABEL_KEYS and e.key not in authored]
53 assert not missing, f"config entries missing from strings.json: {missing}"
54
55
56async def test_strings_json_has_media_and_manifest_sections() -> None:
57 """strings.json ships the media and manifest sections upstream authored."""
58 strings = _load_strings()
59 assert "folder" in strings["media"]
60 assert "playlist" in strings["media"]
61 assert "recommendations" in strings["media"]
62 assert strings["manifest"]["description"]
63
64
65async def test_config_entries_have_no_hardcoded_labels() -> None:
66 """Static entries author their text in strings.json, not in code."""
67 entries = await _get_entries()
68 offenders = [
69 e.key
70 for e in entries
71 if e.key not in _DYNAMIC_LABEL_KEYS and (e.label or e.description or e.action_label)
72 ]
73 assert not offenders, f"entries with hardcoded user-facing text: {offenders}"
74
75
76async def test_quality_options_use_value_first_signature() -> None:
77 """
78 Quality options store the QUALITY_* constants as their values.
79
80 Guards against the legacy (title, value) positional order, which the
81 current models interpret as value=title â silently corrupting the
82 stored setting.
83 """
84 entries = await _get_entries()
85 quality = next(e for e in entries if e.key == "quality")
86 assert quality.options is not None
87 values = {o.value for o in quality.options}
88 assert values == {
89 QUALITY_EFFICIENT,
90 QUALITY_BALANCED,
91 QUALITY_HIGH,
92 QUALITY_SUPERB,
93 }
94
95
96def _media_label_provider(authored: str | None) -> mock.MagicMock:
97 """Build a provider stand-in whose translations lookup returns *authored*."""
98 provider = mock.MagicMock()
99 provider.domain = "yandex_music"
100 provider.mass.translations.get_translation.return_value = authored
101 return provider
102
103
104async def test_media_label_falls_back_verbatim() -> None:
105 """Unauthored media keys keep their (already localized) name verbatim."""
106 provider = _media_label_provider(None)
107 name, translation_key = YandexMusicProvider._media_label(
108 cast("YandexMusicProvider", provider), "folder", "landing_tag_rock", "Рок"
109 )
110 assert name == "Рок"
111 assert translation_key is None
112
113
114async def test_media_label_returns_authored_name_and_key() -> None:
115 """Authored media keys resolve to the English source name plus the key."""
116 provider = _media_label_provider("My Wave")
117 name, translation_key = YandexMusicProvider._media_label(
118 cast("YandexMusicProvider", provider), "playlist", "my_wave", "fallback"
119 )
120 assert name == "My Wave"
121 assert translation_key == "my_wave"
122