Import the vocalinux source tree (tray app, Next.js site in web/, packaging, installers, docs and tests) on top of the Gitea template commit. The project README replaces the template README, and the template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept.
308 lines
13 KiB
Python
308 lines
13 KiB
Python
"""
|
|
Additional coverage tests for config_manager.py module.
|
|
|
|
Tests for uncovered config manager methods.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
# Autouse fixture to prevent sys.modules pollution
|
|
@pytest.fixture(autouse=True)
|
|
def _restore_sys_modules():
|
|
saved = dict(sys.modules)
|
|
yield
|
|
added = set(sys.modules.keys()) - set(saved.keys())
|
|
for k in added:
|
|
del sys.modules[k]
|
|
for k, v in saved.items():
|
|
if k not in sys.modules or sys.modules[k] is not v:
|
|
sys.modules[k] = v
|
|
|
|
|
|
class TestConfigManagerVoiceCommands:
|
|
"""Tests for voice commands enabled configuration."""
|
|
|
|
def test_is_voice_commands_enabled_explicit_true(self):
|
|
"""Test is_voice_commands_enabled when explicitly set to True."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(
|
|
json.dumps({"speech_recognition": {"voice_commands_enabled": True}})
|
|
)
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
assert manager.is_voice_commands_enabled() is True
|
|
|
|
def test_is_voice_commands_enabled_explicit_false(self):
|
|
"""Test is_voice_commands_enabled when explicitly set to False."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(
|
|
json.dumps({"speech_recognition": {"voice_commands_enabled": False}})
|
|
)
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
assert manager.is_voice_commands_enabled() is False
|
|
|
|
def test_is_voice_commands_enabled_auto_vosk(self):
|
|
"""Test is_voice_commands_enabled auto mode with VOSK engine."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(
|
|
json.dumps(
|
|
{"speech_recognition": {"engine": "vosk", "voice_commands_enabled": None}}
|
|
)
|
|
)
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
# VOSK should have voice commands enabled
|
|
assert manager.is_voice_commands_enabled() is True
|
|
|
|
def test_is_voice_commands_enabled_auto_whisper(self):
|
|
"""Test is_voice_commands_enabled auto mode with Whisper engine."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(
|
|
json.dumps(
|
|
{"speech_recognition": {"engine": "whisper", "voice_commands_enabled": None}}
|
|
)
|
|
)
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
# Whisper should have voice commands disabled
|
|
assert manager.is_voice_commands_enabled() is False
|
|
|
|
def test_is_voice_commands_enabled_auto_whisper_cpp(self):
|
|
"""Test is_voice_commands_enabled auto mode with whisper_cpp engine."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(
|
|
json.dumps(
|
|
{
|
|
"speech_recognition": {
|
|
"engine": "whisper_cpp",
|
|
"voice_commands_enabled": None,
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
# whisper_cpp should have voice commands disabled
|
|
assert manager.is_voice_commands_enabled() is False
|
|
|
|
|
|
class TestConfigManagerUpdateSettings:
|
|
"""Tests for update_speech_recognition_settings method."""
|
|
|
|
def test_update_speech_recognition_settings(self):
|
|
"""Test updating speech recognition settings."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(
|
|
json.dumps({"speech_recognition": {"engine": "vosk", "model_size": "small"}})
|
|
)
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
|
|
# Update settings
|
|
manager.update_speech_recognition_settings(
|
|
{"engine": "whisper_cpp", "model_size": "medium", "language": "es"}
|
|
)
|
|
|
|
assert manager.config["speech_recognition"]["engine"] == "whisper_cpp"
|
|
assert manager.config["speech_recognition"]["language"] == "es"
|
|
|
|
def test_update_speech_recognition_settings_creates_section(self):
|
|
"""Test that update creates speech_recognition section if missing."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(json.dumps({}))
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
|
|
manager.update_speech_recognition_settings(
|
|
{"engine": "whisper_cpp", "model_size": "small"}
|
|
)
|
|
|
|
assert "speech_recognition" in manager.config
|
|
assert manager.config["speech_recognition"]["engine"] == "whisper_cpp"
|
|
|
|
|
|
class TestConfigManagerSoundEffects:
|
|
"""Tests for sound effects configuration."""
|
|
|
|
def test_is_sound_effects_enabled_default(self):
|
|
"""Test sound effects enabled by default."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(json.dumps({}))
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
assert manager.is_sound_effects_enabled() is True
|
|
|
|
def test_is_sound_effects_enabled_explicit_false(self):
|
|
"""Test sound effects when explicitly disabled."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(json.dumps({"sound_effects": {"enabled": False}}))
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
assert manager.is_sound_effects_enabled() is False
|
|
|
|
def test_set_sound_effects_enabled(self):
|
|
"""Test setting sound effects enabled."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(json.dumps({}))
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
|
|
# Initially should be enabled (default)
|
|
assert manager.is_sound_effects_enabled() is True
|
|
|
|
# Disable
|
|
manager.set_sound_effects_enabled(False)
|
|
assert manager.is_sound_effects_enabled() is False
|
|
|
|
# Enable again
|
|
manager.set_sound_effects_enabled(True)
|
|
assert manager.is_sound_effects_enabled() is True
|
|
|
|
def test_set_sound_effects_creates_section(self):
|
|
"""Test that set_sound_effects_enabled creates section if missing."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(json.dumps({}))
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = json.loads(config_file.read_text())
|
|
|
|
manager.set_sound_effects_enabled(False)
|
|
assert "sound_effects" in manager.config
|
|
assert manager.config["sound_effects"]["enabled"] is False
|
|
|
|
def test_missing_tone_key_lands_on_voca(self):
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
manager = ConfigManager.__new__(ConfigManager)
|
|
manager.config = {"sound_effects": {"enabled": True}}
|
|
assert manager.get_sound_effects_tone() == "voca"
|
|
|
|
def test_unknown_tone_lands_on_voca(self):
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
manager = ConfigManager.__new__(ConfigManager)
|
|
manager.config = {"sound_effects": {"enabled": True, "tone": "fifth"}}
|
|
assert manager.get_sound_effects_tone() == "voca"
|
|
assert manager.config["sound_effects"]["tone"] == "fifth"
|
|
|
|
def test_explicit_saved_tone_is_left_alone(self):
|
|
from vocalinux.ui.config_manager import SOUND_EFFECT_TONE_IDS, ConfigManager
|
|
|
|
manager = ConfigManager.__new__(ConfigManager)
|
|
for tone_id in SOUND_EFFECT_TONE_IDS:
|
|
manager.config = {"sound_effects": {"enabled": True, "tone": tone_id}}
|
|
assert manager.get_sound_effects_tone() == tone_id
|
|
|
|
def test_set_sound_effects_tone_persists(self):
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
manager = ConfigManager.__new__(ConfigManager)
|
|
manager.config = {}
|
|
manager.set_sound_effects_tone("off")
|
|
assert manager.get_sound_effects_tone() == "off"
|
|
manager.set_sound_effects_tone("lift")
|
|
assert manager.get_sound_effects_tone() == "lift"
|
|
|
|
|
|
class TestConfigManagerUpdateDictRecursive:
|
|
"""Tests for _update_dict_recursive method."""
|
|
|
|
def test_update_dict_recursive_simple(self):
|
|
"""Test recursive dict update with simple values."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(json.dumps({}))
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = {"a": {"b": 1, "c": 2}, "d": 3}
|
|
|
|
manager._update_dict_recursive(manager.config, {"a": {"b": 10}, "d": 30})
|
|
|
|
assert manager.config["a"]["b"] == 10
|
|
assert manager.config["a"]["c"] == 2 # unchanged
|
|
assert manager.config["d"] == 30
|
|
|
|
def test_update_dict_recursive_nested(self):
|
|
"""Test recursive dict update with deeply nested dicts."""
|
|
from vocalinux.ui.config_manager import ConfigManager
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
config_file = Path(tmp_dir) / "config.json"
|
|
config_file.write_text(json.dumps({}))
|
|
|
|
with patch.dict(os.environ, {"XDG_CONFIG_HOME": tmp_dir}, clear=False):
|
|
manager = ConfigManager()
|
|
manager.config = {"level1": {"level2": {"level3": "original"}}}
|
|
|
|
manager._update_dict_recursive(
|
|
manager.config, {"level1": {"level2": {"level3": "updated"}}}
|
|
)
|
|
|
|
assert manager.config["level1"]["level2"]["level3"] == "updated"
|