/
/
/
1"""Tests for the app_vars resolver."""
2
3from __future__ import annotations
4
5import base64
6import json
7import os
8from collections.abc import Iterator
9from pathlib import Path
10
11import pytest
12
13from music_assistant.helpers import app_vars as app_vars_module
14from music_assistant.helpers.app_vars import APP_VAR_NAMES, app_var
15
16
17@pytest.fixture(autouse=True)
18def _clear_caches() -> Iterator[None]:
19 """Reset the module-level lookup caches around every test."""
20 app_vars_module._bundled.cache_clear()
21 app_vars_module._read_json_map.cache_clear()
22 yield
23 app_vars_module._bundled.cache_clear()
24 app_vars_module._read_json_map.cache_clear()
25
26
27def _bundle_blob(salt: bytes, values: dict[str, str]) -> str:
28 """Build an app_secrets.json blob the way the generator does."""
29 secrets = {
30 name: base64.b64encode(app_vars_module._codec(salt, name, value.encode())).decode()
31 for name, value in values.items()
32 }
33 return json.dumps({"salt": base64.b64encode(salt).decode(), "secrets": secrets})
34
35
36def test_unprovisioned_returns_empty(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
37 """With no override, no bundle and no file, an app var resolves to ''."""
38 monkeypatch.delenv("MASS_APP_VAR_SPOTIFY_CLIENT_ID", raising=False)
39 monkeypatch.setattr(app_vars_module, "_bundled", lambda: None)
40 monkeypatch.setenv("MASS_APP_VARS_FILE", str(tmp_path / "does_not_exist.json"))
41 assert app_var("spotify_client_id") == ""
42
43
44def test_unknown_key_returns_empty(monkeypatch: pytest.MonkeyPatch) -> None:
45 """An unknown name resolves to '' rather than raising."""
46 monkeypatch.delenv("MASS_APP_VAR_NOPE_NOT_A_KEY", raising=False)
47 monkeypatch.setattr(app_vars_module, "_bundled", lambda: None)
48 assert app_var("nope_not_a_key") == ""
49
50
51def test_env_override_wins(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
52 """A MASS_APP_VAR_<NAME> override beats a value present in a local file."""
53 file = tmp_path / "app_vars.json"
54 file.write_text(json.dumps({"spotify_client_id": "from_file"}), encoding="utf-8")
55 monkeypatch.setattr(app_vars_module, "_bundled", lambda: None)
56 monkeypatch.setenv("MASS_APP_VARS_FILE", str(file))
57 monkeypatch.setenv("MASS_APP_VAR_SPOTIFY_CLIENT_ID", "from_env")
58 assert app_var("spotify_client_id") == "from_env"
59
60
61def test_local_json_file_via_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
62 """A maintainer's app_vars.json is read when pointed at by MASS_APP_VARS_FILE."""
63 file = tmp_path / "app_vars.json"
64 file.write_text(json.dumps({"tidal_client_id": "abc123"}), encoding="utf-8")
65 monkeypatch.delenv("MASS_APP_VAR_TIDAL_CLIENT_ID", raising=False)
66 monkeypatch.setattr(app_vars_module, "_bundled", lambda: None)
67 monkeypatch.setenv("MASS_APP_VARS_FILE", str(file))
68 assert app_var("tidal_client_id") == "abc123"
69
70
71def test_default_local_file(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
72 """When MASS_APP_VARS_FILE is unset, the default data-dir location is used."""
73 file = tmp_path / "app_vars.json"
74 file.write_text(json.dumps({"qobuz_app_id": "default_loc"}), encoding="utf-8")
75 monkeypatch.delenv("MASS_APP_VARS_FILE", raising=False)
76 monkeypatch.delenv("MASS_APP_VAR_QOBUZ_APP_ID", raising=False)
77 monkeypatch.setattr(app_vars_module, "_bundled", lambda: None)
78 monkeypatch.setattr(app_vars_module, "_DEFAULT_LOCAL_FILE", file)
79 assert app_var("qobuz_app_id") == "default_loc"
80
81
82def test_bundled_roundtrip(monkeypatch: pytest.MonkeyPatch) -> None:
83 """A real app_secrets.json blob parses and decodes back to the original values."""
84 salt = os.urandom(16)
85 values = {name: f"value-for-{name}" for name in APP_VAR_NAMES}
86 monkeypatch.setattr(app_vars_module, "_bundled_text", lambda: _bundle_blob(salt, values))
87 for name, expected in values.items():
88 monkeypatch.delenv(f"MASS_APP_VAR_{name.upper()}", raising=False)
89 assert app_var(name) == expected
90
91
92def test_corrupt_bundle_does_not_crash(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
93 """A malformed bundle resolves to None (and '') instead of raising."""
94 monkeypatch.delenv("MASS_APP_VAR_SPOTIFY_CLIENT_ID", raising=False)
95 monkeypatch.setenv("MASS_APP_VARS_FILE", str(tmp_path / "does_not_exist.json"))
96 for blob in ("{ not valid json", json.dumps({"secrets": {}}), json.dumps({"salt": "!!"})):
97 app_vars_module._bundled.cache_clear()
98 monkeypatch.setattr(app_vars_module, "_bundled_text", lambda blob=blob: blob)
99 assert app_vars_module._bundled() is None
100 assert app_var("spotify_client_id") == ""
101