Files
linux-voice-assistant/tests/unit/test_models.py
T
Omar Amin 8303a96279 Fix peripheral volume_muted state (#403)
Enable updating the `volume_muted` status when muting/unmuting the media player volume, and improve its tracking.
2026-08-17 20:23:16 +03:00

285 lines
9.7 KiB
Python

"""Unit tests for shared models."""
import json
from unittest.mock import Mock, patch
import pytest
from tests.unit.conftest import make_state as make_server_state
def make_preferences(**kwargs):
from linux_voice_assistant.models import Preferences
return Preferences(**kwargs)
# ---------------------------------------------------------------------------
# WakeWordType
# ---------------------------------------------------------------------------
class TestWakeWordType:
def test_micro_value(self):
from linux_voice_assistant.models import WakeWordType
assert WakeWordType.MICRO_WAKE_WORD == "micro"
def test_open_wake_word_value(self):
from linux_voice_assistant.models import WakeWordType
assert WakeWordType.OPEN_WAKE_WORD == "openWakeWord"
def test_is_string_enum(self):
from linux_voice_assistant.models import WakeWordType
assert isinstance(WakeWordType.MICRO_WAKE_WORD, str)
# ---------------------------------------------------------------------------
# Preferences
# ---------------------------------------------------------------------------
class TestPreferences:
def test_default_active_wake_words_is_empty_list(self):
p = make_preferences()
assert p.active_wake_words == []
def test_default_volume_is_none(self):
p = make_preferences()
assert p.volume is None
def test_default_thinking_sound_is_zero(self):
p = make_preferences()
assert p.thinking_sound == 0
def test_default_mic_auto_gain_is_zero(self):
p = make_preferences()
assert p.mic_auto_gain == 0
def test_default_mic_noise_suppression_is_zero(self):
p = make_preferences()
assert p.mic_noise_suppression == 0
def test_custom_values_stored(self):
p = make_preferences(
volume=0.8,
mic_auto_gain=5,
mic_noise_suppression=2,
thinking_sound=1,
)
assert p.volume == 0.8
assert p.mic_auto_gain == 5
assert p.mic_noise_suppression == 2
assert p.thinking_sound == 1
def test_active_wake_words_are_independent_per_instance(self):
"""Mutable default must not be shared between instances."""
p1 = make_preferences()
p2 = make_preferences()
p1.active_wake_words.append("okay_nabu")
assert p2.active_wake_words == []
# ---------------------------------------------------------------------------
# ServerState.save_preferences()
# ---------------------------------------------------------------------------
class TestSavePreferences:
def test_creates_preferences_file(self, tmp_path):
state = make_server_state(tmp_path)
state.save_preferences()
assert state.preferences_path.exists()
def test_saved_json_is_valid(self, tmp_path):
state = make_server_state(tmp_path)
state.save_preferences()
with open(state.preferences_path) as f:
data = json.load(f)
assert isinstance(data, dict)
def test_saved_json_contains_expected_keys(self, tmp_path):
state = make_server_state(tmp_path)
state.save_preferences()
with open(state.preferences_path) as f:
data = json.load(f)
assert "mic_auto_gain" in data
assert "mic_noise_suppression" in data
assert "volume" in data
assert "thinking_sound" in data
def test_saved_values_match_preferences(self, tmp_path):
from linux_voice_assistant.models import Preferences
prefs = Preferences(volume=0.75, mic_auto_gain=3, mic_noise_suppression=1)
state = make_server_state(tmp_path, preferences=prefs)
state.save_preferences()
with open(state.preferences_path) as f:
data = json.load(f)
assert data["volume"] == 0.75
assert data["mic_auto_gain"] == 3
assert data["mic_noise_suppression"] == 1
def test_creates_parent_directory_if_missing(self, tmp_path):
nested_path = tmp_path / "nested" / "dir" / "preferences.json"
state = make_server_state(tmp_path, preferences_path=nested_path)
state.save_preferences()
assert nested_path.exists()
# ---------------------------------------------------------------------------
# ServerState.persist_volume()
# ---------------------------------------------------------------------------
class TestPersistVolume:
def test_updates_volume_on_state(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_volume(0.5)
assert state.volume == 0.5
def test_updates_volume_on_preferences(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_volume(0.5)
assert state.preferences.volume == 0.5
def test_clamps_above_one(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_volume(1.5)
assert state.volume == 1.0
def test_clamps_below_zero(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_volume(-0.5)
assert state.volume == 0.0
def test_saves_to_file(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_volume(0.6)
with open(state.preferences_path) as f:
data = json.load(f)
assert data["volume"] == pytest.approx(0.6)
def test_skips_save_when_volume_unchanged(self, tmp_path):
from linux_voice_assistant.models import Preferences
prefs = Preferences(volume=0.5)
state = make_server_state(tmp_path, preferences=prefs, volume=0.5)
with patch.object(state, "save_preferences") as mock_save:
state.persist_volume(0.5)
mock_save.assert_not_called()
def test_saves_when_volume_changed(self, tmp_path):
from linux_voice_assistant.models import Preferences
prefs = Preferences(volume=0.5)
state = make_server_state(tmp_path, preferences=prefs, volume=0.5)
with patch.object(state, "save_preferences") as mock_save:
state.persist_volume(0.8)
mock_save.assert_called_once()
def test_boundary_value_zero(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_volume(0.0)
assert state.volume == 0.0
def test_boundary_value_one(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_volume(1.0)
assert state.volume == 1.0
def test_emits_volume_muted_event_when_volume_zero(self, tmp_path):
from linux_voice_assistant.peripheral_api import LVAEvent
state = make_server_state(tmp_path)
state.peripheral_api = Mock()
state.persist_volume(0.0)
state.peripheral_api.emit_event_sync.assert_any_call(LVAEvent.VOLUME_CHANGED, {"volume": 0.0})
state.peripheral_api.emit_event_sync.assert_any_call(LVAEvent.VOLUME_MUTED, {"muted": True})
# ---------------------------------------------------------------------------
# ServerState.persist_mic_gain()
# ---------------------------------------------------------------------------
class TestPersistMicGain:
def test_updates_mic_auto_gain_on_state(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_mic_gain(5.0)
assert state.mic_auto_gain == 5
def test_updates_mic_auto_gain_on_preferences(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_mic_gain(5.0)
assert state.preferences.mic_auto_gain == 5
def test_converts_float_to_int(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_mic_gain(7.9)
assert state.mic_auto_gain == 7
def test_skips_save_when_gain_unchanged(self, tmp_path):
state = make_server_state(tmp_path)
state.mic_auto_gain = 3
state.preferences.mic_auto_gain = 3
with patch.object(state, "save_preferences") as mock_save:
state.persist_mic_gain(3.0)
mock_save.assert_not_called()
def test_saves_when_gain_changed(self, tmp_path):
state = make_server_state(tmp_path)
state.mic_auto_gain = 3
state.preferences.mic_auto_gain = 3
with patch.object(state, "save_preferences") as mock_save:
state.persist_mic_gain(10.0)
mock_save.assert_called_once()
# ---------------------------------------------------------------------------
# ServerState.persist_mic_noise()
# ---------------------------------------------------------------------------
class TestPersistMicNoise:
def test_updates_mic_noise_suppression_on_state(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_mic_noise(2.0)
assert state.mic_noise_suppression == 2
def test_updates_mic_noise_suppression_on_preferences(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_mic_noise(2.0)
assert state.preferences.mic_noise_suppression == 2
def test_converts_float_to_int(self, tmp_path):
state = make_server_state(tmp_path)
state.persist_mic_noise(3.9)
assert state.mic_noise_suppression == 3
def test_skips_save_when_noise_unchanged(self, tmp_path):
state = make_server_state(tmp_path)
state.mic_noise_suppression = 2
state.preferences.mic_noise_suppression = 2
with patch.object(state, "save_preferences") as mock_save:
state.persist_mic_noise(2.0)
mock_save.assert_not_called()
def test_saves_when_noise_changed(self, tmp_path):
state = make_server_state(tmp_path)
state.mic_noise_suppression = 2
state.preferences.mic_noise_suppression = 2
with patch.object(state, "save_preferences") as mock_save:
state.persist_mic_noise(4.0)
mock_save.assert_called_once()