"""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