/
/
/
1"""Drift guard between ``provider/strings.json`` and the de-literalized config schema."""
2
3from __future__ import annotations
4
5import json
6from pathlib import Path
7from typing import TYPE_CHECKING, Any
8
9from music_assistant_models.enums import ConfigEntryType
10
11from music_assistant.providers.fastmcp_server import config as _config
12from music_assistant.providers.fastmcp_server.config import build_config_entries
13from music_assistant.providers.fastmcp_server.constants import DEFAULT_MOUNT_PATH
14
15if TYPE_CHECKING:
16 from unittest.mock import MagicMock
17
18# Categories that MA core resolves from its own shared translation set, so they
19# need no entry in this provider's ``config_categories``.
20COMMON_CATEGORIES = {"server", "debug", "generic", "advanced"}
21
22# Locate strings.json next to the config module so the path holds both here and
23# when the provider is inlined upstream (``provider`` -> the inlined package).
24STRINGS_PATH = Path(_config.__file__).resolve().parent / "strings.json"
25
26
27def _load_strings() -> dict[str, Any]:
28 data: dict[str, Any] = json.loads(STRINGS_PATH.read_text(encoding="utf-8"))
29 return data
30
31
32def test_strings_json_is_valid_with_required_keys() -> None:
33 """``strings.json`` is valid JSON exposing ``config_entries`` and ``config_categories``."""
34 data = _load_strings()
35 assert isinstance(data, dict)
36 assert "config_entries" in data
37 assert "config_categories" in data
38
39
40def test_every_category_is_known_or_declared(mock_mass: MagicMock) -> None:
41 """Each category emitted by the schema is a common one or declared in ``config_categories``."""
42 data = _load_strings()
43 declared = set(data["config_categories"])
44 for entry in build_config_entries(mock_mass, DEFAULT_MOUNT_PATH):
45 category = getattr(entry, "category", None)
46 if not category:
47 continue
48 assert category in COMMON_CATEGORIES or category in declared, (
49 f"category {category!r} is neither common nor declared in config_categories"
50 )
51
52
53def test_static_entries_carry_no_inline_text(mock_mass: MagicMock) -> None:
54 """
55 All static entries are de-literalized â ``strings.json`` owns their text.
56
57 Only ``LABEL``-type entries may carry inline text: their content is composed
58 at runtime (e.g. the endpoint info label embeds the live ``base_url``).
59 """
60 for entry in build_config_entries(mock_mass, DEFAULT_MOUNT_PATH):
61 if entry.type is ConfigEntryType.LABEL:
62 continue
63 assert entry.label is None, f"inline label on {entry.key!r}"
64 assert entry.description is None, f"inline description on {entry.key!r}"
65
66
67def test_deliteralized_entries_have_strings(mock_mass: MagicMock) -> None:
68 """Every static entry with ``label is None`` has full label+description text in strings.json."""
69 data = _load_strings()
70 config_entries = data["config_entries"]
71 for entry in build_config_entries(mock_mass, DEFAULT_MOUNT_PATH):
72 if entry.label is not None:
73 continue
74 assert entry.key in config_entries, f"missing strings.json entry for {entry.key!r}"
75 text = config_entries[entry.key]
76 assert text.get("label"), f"empty label for {entry.key!r}"
77 assert text.get("description"), f"empty description for {entry.key!r}"
78