293 lines
12 KiB
Python
293 lines
12 KiB
Python
"""
|
|
Unit tests for settings validation in SettingsManager.
|
|
|
|
Tests the _validate_setting_value method to ensure proper input validation
|
|
for all configurable settings that could cause crashes or accessibility issues.
|
|
"""
|
|
|
|
import pytest
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import Mock
|
|
|
|
# Import the settings manager
|
|
from fenrirscreenreader.core.settingsData import settings_data
|
|
from fenrirscreenreader.core.settingsManager import SettingsManager
|
|
from fenrirscreenreader.commands.commands import save_settings
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.settings
|
|
class TestSpeechSettingsValidation:
|
|
"""Test validation of speech-related settings."""
|
|
|
|
def setup_method(self):
|
|
"""Create a SettingsManager instance for each test."""
|
|
self.manager = SettingsManager()
|
|
|
|
def test_speech_rate_valid_range(self):
|
|
"""Speech rate should accept values between 0.0 and 3.0."""
|
|
# Valid boundary values
|
|
self.manager._validate_setting_value("speech", "rate", 0.0)
|
|
self.manager._validate_setting_value("speech", "rate", 1.5)
|
|
self.manager._validate_setting_value("speech", "rate", 3.0)
|
|
|
|
def test_speech_rate_rejects_negative(self):
|
|
"""Speech rate should reject negative values."""
|
|
with pytest.raises(ValueError, match="must be between 0.0 and 3.0"):
|
|
self.manager._validate_setting_value("speech", "rate", -0.1)
|
|
|
|
def test_speech_rate_rejects_too_high(self):
|
|
"""Speech rate should reject values above 3.0."""
|
|
with pytest.raises(ValueError, match="must be between 0.0 and 3.0"):
|
|
self.manager._validate_setting_value("speech", "rate", 10.0)
|
|
|
|
def test_speech_pitch_valid_range(self):
|
|
"""Speech pitch should accept values between 0.0 and 2.0."""
|
|
self.manager._validate_setting_value("speech", "pitch", 0.0)
|
|
self.manager._validate_setting_value("speech", "pitch", 1.0)
|
|
self.manager._validate_setting_value("speech", "pitch", 2.0)
|
|
|
|
def test_speech_pitch_rejects_invalid(self):
|
|
"""Speech pitch should reject out-of-range values."""
|
|
with pytest.raises(ValueError, match="must be between 0.0 and 2.0"):
|
|
self.manager._validate_setting_value("speech", "pitch", -1.0)
|
|
with pytest.raises(ValueError, match="must be between 0.0 and 2.0"):
|
|
self.manager._validate_setting_value("speech", "pitch", 5.0)
|
|
|
|
def test_speech_volume_valid_range(self):
|
|
"""Speech volume should accept values between 0.0 and 1.5."""
|
|
self.manager._validate_setting_value("speech", "volume", 0.0)
|
|
self.manager._validate_setting_value("speech", "volume", 1.0)
|
|
self.manager._validate_setting_value("speech", "volume", 1.5)
|
|
|
|
def test_speech_volume_rejects_negative(self):
|
|
"""Speech volume should reject negative values."""
|
|
with pytest.raises(ValueError, match="must be between 0.0 and 1.5"):
|
|
self.manager._validate_setting_value("speech", "volume", -0.5)
|
|
|
|
def test_speech_driver_whitelisted(self):
|
|
"""Speech driver should only accept whitelisted values."""
|
|
# Valid drivers
|
|
self.manager._validate_setting_value("speech", "driver", "speechdDriver")
|
|
self.manager._validate_setting_value("speech", "driver", "genericDriver")
|
|
self.manager._validate_setting_value("speech", "driver", "dummyDriver")
|
|
|
|
# Invalid driver
|
|
with pytest.raises(ValueError, match="Invalid speech driver"):
|
|
self.manager._validate_setting_value(
|
|
"speech", "driver", "nonexistentDriver"
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.settings
|
|
class TestSoundSettingsValidation:
|
|
"""Test validation of sound-related settings."""
|
|
|
|
def setup_method(self):
|
|
"""Create a SettingsManager instance for each test."""
|
|
self.manager = SettingsManager()
|
|
|
|
def test_sound_volume_valid_range(self):
|
|
"""Sound volume should accept values between 0.0 and 1.5."""
|
|
self.manager._validate_setting_value("sound", "volume", 0.0)
|
|
self.manager._validate_setting_value("sound", "volume", 0.7)
|
|
self.manager._validate_setting_value("sound", "volume", 1.5)
|
|
|
|
def test_sound_volume_rejects_invalid(self):
|
|
"""Sound volume should reject out-of-range values."""
|
|
with pytest.raises(ValueError, match="must be between 0.0 and 1.5"):
|
|
self.manager._validate_setting_value("sound", "volume", -0.1)
|
|
with pytest.raises(ValueError, match="must be between 0.0 and 1.5"):
|
|
self.manager._validate_setting_value("sound", "volume", 2.0)
|
|
|
|
def test_sound_driver_whitelisted(self):
|
|
"""Sound driver should only accept whitelisted values."""
|
|
# Valid drivers
|
|
self.manager._validate_setting_value("sound", "driver", "genericDriver")
|
|
self.manager._validate_setting_value("sound", "driver", "gstreamerDriver")
|
|
self.manager._validate_setting_value("sound", "driver", "dummyDriver")
|
|
|
|
# Invalid driver
|
|
with pytest.raises(ValueError, match="Invalid sound driver"):
|
|
self.manager._validate_setting_value("sound", "driver", "invalidDriver")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.settings
|
|
class TestDriverValidation:
|
|
"""Test validation of driver selection settings."""
|
|
|
|
def setup_method(self):
|
|
"""Create a SettingsManager instance for each test."""
|
|
self.manager = SettingsManager()
|
|
|
|
def test_screen_driver_whitelisted(self):
|
|
"""Screen driver should only accept whitelisted values."""
|
|
# Valid drivers
|
|
self.manager._validate_setting_value("screen", "driver", "vcsaDriver")
|
|
self.manager._validate_setting_value("screen", "driver", "ptyDriver")
|
|
self.manager._validate_setting_value("screen", "driver", "dummyDriver")
|
|
|
|
# Invalid driver
|
|
with pytest.raises(ValueError, match="Invalid screen driver"):
|
|
self.manager._validate_setting_value("screen", "driver", "unknownDriver")
|
|
|
|
def test_keyboard_driver_whitelisted(self):
|
|
"""Keyboard driver should only accept whitelisted values."""
|
|
# Valid drivers
|
|
self.manager._validate_setting_value("keyboard", "driver", "evdevDriver")
|
|
self.manager._validate_setting_value("keyboard", "driver", "x11Driver")
|
|
self.manager._validate_setting_value("keyboard", "driver", "dummyDriver")
|
|
|
|
with pytest.raises(ValueError, match="Invalid input driver"):
|
|
self.manager._validate_setting_value("keyboard", "driver", "ptyDriver")
|
|
with pytest.raises(ValueError, match="Invalid input driver"):
|
|
self.manager._validate_setting_value("keyboard", "driver", "atspiDriver")
|
|
|
|
# Invalid driver
|
|
with pytest.raises(ValueError, match="Invalid input driver"):
|
|
self.manager._validate_setting_value("keyboard", "driver", "badDriver")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.settings
|
|
class TestGeneralSettingsValidation:
|
|
"""Test validation of general settings."""
|
|
|
|
def setup_method(self):
|
|
"""Create a SettingsManager instance for each test."""
|
|
self.manager = SettingsManager()
|
|
|
|
def test_debug_level_valid_range(self):
|
|
"""Debug level should accept values 0-3."""
|
|
self.manager._validate_setting_value("general", "debug_level", 0)
|
|
self.manager._validate_setting_value("general", "debug_level", 1)
|
|
self.manager._validate_setting_value("general", "debug_level", 2)
|
|
self.manager._validate_setting_value("general", "debug_level", 3)
|
|
|
|
def test_debug_level_rejects_invalid(self):
|
|
"""Debug level should reject values outside 0-3."""
|
|
with pytest.raises(ValueError, match="must be between 0 and 3"):
|
|
self.manager._validate_setting_value("general", "debug_level", -1)
|
|
with pytest.raises(ValueError, match="must be between 0 and 3"):
|
|
self.manager._validate_setting_value("general", "debug_level", 10)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.settings
|
|
class TestValidationSkipsUnknownSettings:
|
|
"""Test that validation doesn't error on unknown settings."""
|
|
|
|
def setup_method(self):
|
|
"""Create a SettingsManager instance for each test."""
|
|
self.manager = SettingsManager()
|
|
|
|
def test_unknown_section_no_error(self):
|
|
"""Unknown sections should not raise errors during validation."""
|
|
# Should not raise - validation only applies to known critical settings
|
|
self.manager._validate_setting_value("unknown_section", "setting", "value")
|
|
|
|
def test_unknown_setting_no_error(self):
|
|
"""Unknown settings in known sections should not raise errors."""
|
|
# Should not raise - only specific critical settings are validated
|
|
self.manager._validate_setting_value("speech", "unknown_setting", "value")
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.settings
|
|
def test_focus_settings_define_tui_toggle():
|
|
"""Focus settings should include the TUI toggle used by on-screen handlers."""
|
|
assert settings_data["focus"]["tui"] is False
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.settings
|
|
class TestSettingsPathSelection:
|
|
"""Test root/user settings load and save path selection."""
|
|
|
|
def setup_method(self):
|
|
self.manager = SettingsManager()
|
|
|
|
def configure_paths(self, tmp_path):
|
|
system_root = tmp_path / "etc" / "fenrirscreenreader"
|
|
system_file = system_root / "settings" / "settings.conf"
|
|
user_file = (
|
|
tmp_path
|
|
/ "home"
|
|
/ "Username"
|
|
/ ".local"
|
|
/ "share"
|
|
/ "stormux"
|
|
/ "fenrirscreenreader"
|
|
/ "settings"
|
|
/ "settings.conf"
|
|
)
|
|
self.manager.system_settings_root = str(system_root) + "/"
|
|
self.manager.system_settings_file = str(system_file)
|
|
self.manager.user_settings_file = str(user_file)
|
|
return system_file, user_file
|
|
|
|
def test_non_root_loads_user_settings_when_present(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
system_file, user_file = self.configure_paths(tmp_path)
|
|
system_file.parent.mkdir(parents=True)
|
|
system_file.write_text("[general]\n", encoding="utf-8")
|
|
user_file.parent.mkdir(parents=True)
|
|
user_file.write_text("[general]\n", encoding="utf-8")
|
|
monkeypatch.setattr("os.geteuid", lambda: 1000)
|
|
|
|
assert self.manager.resolve_settings_file() == str(user_file)
|
|
|
|
def test_non_root_falls_back_to_system_settings(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
system_file, _user_file = self.configure_paths(tmp_path)
|
|
system_file.parent.mkdir(parents=True)
|
|
system_file.write_text("[general]\n", encoding="utf-8")
|
|
monkeypatch.setattr("os.geteuid", lambda: 1000)
|
|
|
|
assert self.manager.resolve_settings_file() == str(system_file)
|
|
|
|
def test_root_uses_system_settings_even_when_user_settings_exists(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
system_file, user_file = self.configure_paths(tmp_path)
|
|
system_file.parent.mkdir(parents=True)
|
|
system_file.write_text("[general]\n", encoding="utf-8")
|
|
user_file.parent.mkdir(parents=True)
|
|
user_file.write_text("[general]\n", encoding="utf-8")
|
|
monkeypatch.setattr("os.geteuid", lambda: 0)
|
|
|
|
assert self.manager.resolve_settings_file() == str(system_file)
|
|
|
|
def test_save_default_path_follows_effective_user(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
system_file, user_file = self.configure_paths(tmp_path)
|
|
|
|
monkeypatch.setattr("os.geteuid", lambda: 1000)
|
|
assert self.manager.get_default_save_settings_file() == str(user_file)
|
|
|
|
monkeypatch.setattr("os.geteuid", lambda: 0)
|
|
assert self.manager.get_default_save_settings_file() == str(system_file)
|
|
|
|
def test_save_settings_command_uses_default_save_path(self):
|
|
manager = Mock()
|
|
output_manager = Mock()
|
|
command = save_settings.command()
|
|
command.initialize(
|
|
{
|
|
"runtime": {
|
|
"SettingsManager": manager,
|
|
"OutputManager": output_manager,
|
|
}
|
|
}
|
|
)
|
|
|
|
command.run()
|
|
|
|
manager.save_settings.assert_called_once_with()
|