""" Shared pytest fixtures for Fenrir tests. This file contains fixtures and configuration used across all test modules. """ import os import sys import tempfile from pathlib import Path from unittest.mock import MagicMock, Mock import pytest # Add src directory to Python path for imports fenrir_root = Path(__file__).parent.parent sys.path.insert(0, str(fenrir_root / "src")) @pytest.fixture def mock_environment(): """Create a minimal mock environment for testing. Returns a mock environment dict with required runtime managers mocked. This allows testing components without initializing the full Fenrir stack. """ env = { "runtime": { "DebugManager": Mock(write_debug_out=Mock()), "OutputManager": Mock( present_text=Mock(), speak_text=Mock(), interrupt_output=Mock(), ), "SettingsManager": Mock( get_setting=Mock(return_value="default"), get_setting_as_int=Mock(return_value=0), get_setting_as_float=Mock(return_value=0.0), get_setting_as_bool=Mock(return_value=True), ), "InputManager": Mock( sendKeys=Mock(), handle_device_grab=Mock(), ), "ScreenManager": Mock(update_screen_ignored=Mock()), "EventManager": Mock(stop_main_event_loop=Mock()), "MemoryManager": Mock( add_value_to_first_index=Mock(), get_index_list_element=Mock(return_value="test clipboard"), is_index_list_empty=Mock(return_value=False), ), "VmenuManager": Mock( set_curr_menu=Mock(), ), "CursorManager": Mock( set_window_for_application=Mock(), clear_window_for_application=Mock(), ), }, "settings": Mock(), "general": { "curr_user": "testuser", }, } return env @pytest.fixture def temp_config_file(tmp_path): """Create a temporary configuration file for testing. Returns path to a valid test configuration file. """ config_path = tmp_path / "test_settings.conf" config_content = """[sound] enabled=True driver=gstreamerDriver theme=default volume=0.7 [speech] enabled=True driver=speechdDriver rate=0.5 pitch=0.5 volume=1.0 autoReadIncoming=True [screen] driver=vcsaDriver encoding=auto screenUpdateDelay=0.05 [keyboard] driver=evdevDriver device=ALL grabDevices=True keyboardLayout=desktop [general] debugLevel=2 debugMode=File [remote] enable=True driver=unixDriver port=22447 enableSettingsRemote=True enableCommandRemote=True """ config_path.write_text(config_content) return config_path @pytest.fixture def temp_socket_path(tmp_path): """Create a temporary Unix socket path for testing. Returns path that can be used for Unix socket testing. """ return tmp_path / "test_fenrir.sock" @pytest.fixture def temp_clipboard_file(tmp_path): """Create a temporary clipboard file for testing. Returns path to a temporary file for clipboard operations. """ clipboard_path = tmp_path / "fenrirClipboard" clipboard_path.write_text("") return clipboard_path @pytest.fixture def sample_screen_data(): """Return sample screen data for testing screen-related functionality. Returns dict with screen dimensions and content. """ return { "columns": 80, "lines": 24, "delta": "Hello World", "cursor": {"x": 0, "y": 0}, "content": "Sample screen content\nSecond line\nThird line", } @pytest.fixture def sample_remote_commands(): """Return sample remote control commands for testing. Returns list of valid remote commands. """ return [ "command say Hello World", "command interrupt", "setting set speech#rate=0.8", "setting set speech#pitch=0.6", "setting set sound#volume=0.5", "setting reset", ] @pytest.fixture def invalid_remote_commands(): """Return invalid remote control commands for testing validation. Returns list of commands that should be rejected. """ return [ "setting set speech#rate=999", # Out of range "setting set speech#rate=-1", # Negative value "setting set speech#pitch=10", # Out of range "setting set speech#volume=-0.5", # Negative volume "setting set invalid#setting=value", # Invalid section "command unknown_command", # Unknown command ] # Pytest hooks for test session customization def pytest_configure(config): """Configure pytest with custom settings.""" # Add custom markers config.addinivalue_line( "markers", "unit: Unit tests (fast, no mocking required)" ) config.addinivalue_line( "markers", "integration: Integration tests (require mocking)" ) config.addinivalue_line( "markers", "driver: Driver tests (require root access)" ) config.addinivalue_line( "markers", "slow: Tests that take more than 1 second" ) def pytest_collection_modifyitems(config, items): """Modify test collection to skip driver tests unless explicitly run. Driver tests require root access and hardware, so skip by default. Run with: pytest --run-driver-tests """ skip_driver = pytest.mark.skip( reason="Driver tests require root access (use --run-driver-tests)" ) run_driver_tests = config.getoption("--run-driver-tests", default=False) for item in items: if "driver" in item.keywords and not run_driver_tests: item.add_marker(skip_driver) def pytest_addoption(parser): """Add custom command line options.""" parser.addoption( "--run-driver-tests", action="store_true", default=False, help="Run driver tests that require root access", )