Files
musicmouse/python-backend/tests/test_startup.py
2026-08-27 12:32:20 +02:00

129 lines
4.4 KiB
Python

"""What the app does when the config leaves the hardware out.
Both of these are deliberately warnings rather than errors: a spare machine running
only the web front-end is a supported way to use this, and it should say so once at
startup rather than fail or - worse - look like it is working when it is not.
"""
from __future__ import annotations
import copy
import logging
from pathlib import Path
from typing import Any
import pytest
from musicmouse.__main__ import _build_player, wants_hardware
from musicmouse.bus import EventBus
from musicmouse.clock import RealClock
from musicmouse.config import SIMULATE, Config, ConfigError, load_config
from musicmouse.devices.null_transport import NullTransport
from musicmouse.simulator.fake_player import FakePlayer
from tests.conftest import VALID_CONFIG, write_config
def _config(config_dir: Path, *, drop: tuple[str, ...] = (), **general: Any) -> Config:
data = copy.deepcopy(VALID_CONFIG)
data["general"].update(general)
for key in drop:
data["general"].pop(key, None)
return load_config(write_config(config_dir, data))
# ------------------------------------------------------------------- serial port
def test_a_missing_serial_port_is_an_error(config_dir: Path) -> None:
"""Simulation has to be asked for. A config that lost the line must not boot."""
with pytest.raises(ConfigError) as excinfo:
_config(config_dir, drop=("serial_port",))
assert "general.serial_port" in str(excinfo.value)
assert "required" in str(excinfo.value)
def test_a_missing_alsa_device_is_an_error(config_dir: Path) -> None:
with pytest.raises(ConfigError) as excinfo:
_config(config_dir, drop=("alsa_device",))
assert "general.alsa_device" in str(excinfo.value)
assert "required" in str(excinfo.value)
def test_both_missing_are_reported_together(config_dir: Path) -> None:
with pytest.raises(ConfigError) as excinfo:
_config(config_dir, drop=("serial_port", "alsa_device"))
message = str(excinfo.value)
assert "2 problems" in message
assert "general.serial_port" in message
assert "general.alsa_device" in message
def test_simulate_switches_off_the_serial_link(
config_dir: Path, caplog: pytest.LogCaptureFixture
) -> None:
config = _config(config_dir, serial_port=SIMULATE)
assert config.general.serial_simulated is True
with caplog.at_level(logging.WARNING):
assert wants_hardware(config, False) is False
assert "running without the mouse" in caplog.text
def test_a_real_port_is_used(config_dir: Path) -> None:
config = _config(config_dir)
assert config.general.serial_simulated is False
assert wants_hardware(config, False) is True
def test_the_flag_wins_over_a_configured_port(config_dir: Path) -> None:
assert wants_hardware(_config(config_dir), True) is False
# ------------------------------------------------------------------ audio device
async def test_simulate_gives_a_silent_player(
config_dir: Path, caplog: pytest.LogCaptureFixture
) -> None:
bus = EventBus()
await bus.start()
try:
general = _config(config_dir, alsa_device=SIMULATE).general
assert general.audio_simulated is True
with caplog.at_level(logging.WARNING):
player = _build_player(bus, general, clock=RealClock())
assert isinstance(player, FakePlayer)
assert "alsa_device" in caplog.text
assert "nothing will be audible" in caplog.text
# The clamps still come from the config, so volume behaves the same either way.
assert player.volume == 40
player.set_volume(999)
assert player.volume == 60
finally:
player.close()
await bus.stop()
async def test_an_alsa_device_asks_for_the_real_player(config_dir: Path) -> None:
"""Only that it tries: instantiating VlcPlayer needs libVLC, which CI has not got."""
bus = EventBus()
await bus.start()
try:
general = _config(config_dir, alsa_device="default").general
assert general.audio_simulated is False
with pytest.raises(Exception): # noqa: B017 - ImportError or an OSError from libVLC
_build_player(bus, general, clock=RealClock())
finally:
await bus.stop()
# ---------------------------------------------------------------- null transport
def test_the_null_transport_drops_what_it_is_handed() -> None:
transport = NullTransport()
transport.write(b"\x00\x01\x02")
assert transport.connected is False