109 lines
3.7 KiB
Python
109 lines
3.7 KiB
Python
"""Shared test fixtures/helpers for the unit test suite."""
|
|
|
|
import tempfile
|
|
from dataclasses import MISSING, fields
|
|
from pathlib import Path
|
|
from queue import Queue
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
def _auto_value_for(f):
|
|
"""Best-effort placeholder for a required (no-default) dataclass field."""
|
|
type_str = f.type if isinstance(f.type, str) else getattr(f.type, "__name__", "")
|
|
|
|
if type_str == "str":
|
|
return f"/mock/{f.name}"
|
|
if type_str == "Path":
|
|
return Path(f"/mock/{f.name}")
|
|
if type_str in ("float",):
|
|
return 0.0
|
|
if type_str in ("int",):
|
|
return 0
|
|
if type_str in ("bool",):
|
|
return False
|
|
if type_str.startswith("Set"):
|
|
return set()
|
|
if type_str.startswith(("List", "list")):
|
|
return []
|
|
if type_str.startswith(("Dict", "dict")):
|
|
return {}
|
|
# Fallback for anything else (players, queues, protobuf-ish types, etc.)
|
|
return MagicMock()
|
|
|
|
|
|
def make_state(tmp_path=None, **overrides):
|
|
"""Build a minimal ServerState, auto-filling any required fields
|
|
that aren't explicitly provided via defaults/overrides.
|
|
"""
|
|
from linux_voice_assistant.models import Preferences, ServerState
|
|
|
|
if tmp_path is None:
|
|
tmp_path = Path(tempfile.mkdtemp())
|
|
|
|
stop_word = MagicMock()
|
|
stop_word.id = "stop"
|
|
stop_word.is_active = False
|
|
|
|
values = {
|
|
"name": "lva-test",
|
|
"friendly_name": "LVA Test",
|
|
"mac_address": "aa:bb:cc:dd:ee:ff",
|
|
"ip_address": "192.168.1.1",
|
|
"network_interface": "eth0",
|
|
"version": "1.0.0",
|
|
"esphome_version": "42.0.0",
|
|
"audio_queue": Queue(),
|
|
"entities": [],
|
|
"available_wake_words": {},
|
|
"wake_words": {},
|
|
"active_wake_words": set(),
|
|
"stop_word": stop_word,
|
|
"music_player": MagicMock(),
|
|
"tts_player": MagicMock(),
|
|
"wakeup_sound": "/sounds/wake.flac",
|
|
"start_listening_sound": "/sounds/start_listening.flac",
|
|
"processing_sound": "/sounds/processing.wav",
|
|
"timer_finished_sound": "/sounds/timer.flac",
|
|
"mute_sound": "/sounds/mute.flac",
|
|
"unmute_sound": "/sounds/unmute.flac",
|
|
"button_double_press_sound": "/sounds/double_press.flac",
|
|
"button_triple_press_sound": "/sounds/triple_press.flac",
|
|
"button_long_press_sound": "/sounds/long_press.flac",
|
|
"preferences": Preferences(),
|
|
"preferences_path": tmp_path / "preferences.json",
|
|
"download_dir": tmp_path / "downloads",
|
|
"volume": 1.0,
|
|
"mic_volume": 100,
|
|
"mic_auto_gain": 0,
|
|
"mic_noise_suppression": 0,
|
|
}
|
|
values.update(overrides)
|
|
|
|
# Auto-fill any other required (no-default) field we didn't anticipate.
|
|
for f in fields(ServerState):
|
|
if f.name in values:
|
|
continue
|
|
no_default = f.default is MISSING and f.default_factory is MISSING # type: ignore[misc]
|
|
if no_default:
|
|
values[f.name] = _auto_value_for(f)
|
|
|
|
return ServerState(**values)
|
|
|
|
|
|
def make_satellite(tmp_path=None, state_overrides=None):
|
|
"""Build a VoiceSatelliteProtocol with heavy dependencies mocked."""
|
|
state = make_state(tmp_path, **(state_overrides or {}))
|
|
|
|
with (
|
|
patch("linux_voice_assistant.satellite.WakeWord1SensitivityNumberEntity", MagicMock()),
|
|
patch("linux_voice_assistant.satellite.WakeWord2SensitivityNumberEntity", MagicMock()),
|
|
patch("linux_voice_assistant.satellite.StopWordSensitivityNumberEntity", MagicMock()),
|
|
):
|
|
from linux_voice_assistant.satellite import VoiceSatelliteProtocol
|
|
|
|
satellite = VoiceSatelliteProtocol(state)
|
|
|
|
satellite._writelines = MagicMock()
|
|
satellite._loop = None
|
|
return satellite
|