test(providers): cover the registry and the app's catalogue independence
Registry lookup and its fallback for a stale slug, SomaFM branding and guessed mirrors, provider round-trip in the config, legacy-config migration and BEEFY_*/SOMAFM_* precedence - plus an end-to-end boot against a fake second provider, which is what actually proves the UI no longer knows about SomaFM.
This commit is contained in:
1 parent
c4cc91ee1a
commit
58e9202e25
1 file changed
+130
@@ -0,0 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
import json
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from beefy_player import config
|
||||
from beefy_player.models import Song, Station
|
||||
from beefy_player.providers import PROVIDERS, available_providers, get_provider
|
||||
from beefy_player.providers.base import RadioProvider
|
||||
from beefy_player.providers.somafm import SomaFMProvider
|
||||
|
||||
|
||||
def rendered_text(app) -> str:
|
||||
svg = html.unescape(app.export_screenshot()).replace("\xa0", " ")
|
||||
return re.sub(r"<[^>]+>", " ", svg)
|
||||
|
||||
|
||||
def test_registry_lists_somafm_as_the_default() -> None:
|
||||
assert available_providers() == ("somafm",)
|
||||
assert isinstance(get_provider(), SomaFMProvider)
|
||||
assert get_provider("somafm") is get_provider()
|
||||
# a stale slug in the config file must not stop the app from starting
|
||||
assert get_provider("no-such-catalogue") is get_provider()
|
||||
|
||||
|
||||
def test_somafm_provider_describes_its_catalogue() -> None:
|
||||
provider = get_provider()
|
||||
assert provider.name == "SomaFM"
|
||||
assert provider.brand == "SOMA FM"
|
||||
assert provider.station_source == "somafm.com/channels.json"
|
||||
assert "somafm.com" in provider.user_agent
|
||||
|
||||
stations = provider.fallback_stations()
|
||||
assert len(stations) > 40
|
||||
station = stations[0]
|
||||
assert station.provider == "somafm"
|
||||
assert station.uid == f"somafm:{station.id}"
|
||||
assert all("somafm.com" in url for url in provider.fallback_urls(station))
|
||||
|
||||
|
||||
class FakeProvider(RadioProvider):
|
||||
"""A second catalogue, as small as the interface allows."""
|
||||
|
||||
id = "fake"
|
||||
name = "Fake FM"
|
||||
homepage = "fake.test"
|
||||
brand = "FAKE FM"
|
||||
tagline = "not real radio"
|
||||
station_source = "fake.test/stations.json"
|
||||
history_source = "recent.json"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.station = Station(
|
||||
id="one", title="Fake One", genre="test", provider=self.id
|
||||
)
|
||||
|
||||
async def fetch_stations(self, client):
|
||||
return [self.station]
|
||||
|
||||
def load_cached_stations(self):
|
||||
return [self.station]
|
||||
|
||||
def fallback_stations(self):
|
||||
return [self.station]
|
||||
|
||||
async def fetch_history(self, client, station_id):
|
||||
return [Song(title="Tune", artist="Someone")]
|
||||
|
||||
def load_cached_history(self, station_id):
|
||||
return []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_app_runs_against_a_different_catalogue(monkeypatch) -> None:
|
||||
"""The UI must not know which catalogue it is showing."""
|
||||
from beefy_player.app import BeefyPlayerApp
|
||||
|
||||
fake = FakeProvider()
|
||||
monkeypatch.setitem(PROVIDERS, "fake", fake)
|
||||
app = BeefyPlayerApp(provider="fake", autoplay=False, offline=True, audio=False)
|
||||
assert app.provider is fake
|
||||
async with app.run_test(size=(100, 30)) as pilot:
|
||||
await pilot.pause()
|
||||
assert app.current is not None
|
||||
assert app.current.uid == "fake:one"
|
||||
app.query_one("StagePanel").refresh_stage()
|
||||
await pilot.pause()
|
||||
text = rendered_text(app)
|
||||
assert "FAKE ONE" in text
|
||||
assert "Fake FM" in app.title
|
||||
|
||||
|
||||
def test_config_remembers_the_provider() -> None:
|
||||
assert config.Config.load().provider == "somafm"
|
||||
config.Config(provider="fake").save()
|
||||
assert config.Config.load().provider == "fake"
|
||||
|
||||
|
||||
def test_legacy_config_is_migrated_once(monkeypatch) -> None:
|
||||
legacy = config.LEGACY_CONFIG_PATH
|
||||
legacy.parent.mkdir(parents=True, exist_ok=True)
|
||||
legacy.write_text(
|
||||
json.dumps({"favorites": ["defcon"], "volume": 33, "last_station": "dronezone"})
|
||||
)
|
||||
|
||||
loaded = config.Config.load()
|
||||
assert loaded.favorites == ["defcon"]
|
||||
assert loaded.volume == 33
|
||||
assert loaded.last_station == "dronezone"
|
||||
assert config.CONFIG_PATH.exists()
|
||||
|
||||
# the new file wins from then on
|
||||
config.Config(volume=11).save()
|
||||
legacy.write_text(json.dumps({"volume": 99}))
|
||||
assert config.Config.load().volume == 11
|
||||
|
||||
|
||||
def test_environment_variables_prefer_the_beefy_prefix(monkeypatch) -> None:
|
||||
monkeypatch.setenv("BEEFY_IMAGES", "tgp")
|
||||
monkeypatch.setenv("SOMAFM_IMAGES", "0")
|
||||
assert config.env_var("IMAGES") == "tgp"
|
||||
|
||||
monkeypatch.delenv("BEEFY_IMAGES")
|
||||
assert config.env_var("IMAGES") == "0" # the old spelling still works
|
||||
|
||||
monkeypatch.delenv("SOMAFM_IMAGES")
|
||||
assert config.env_var("IMAGES") == ""
|
||||
Reference in new issue
Block a user