A Python port of the Invisible Internet Project (I2P)
at main 139 lines 4.9 kB view raw
1"""Tests for desktop tray runner — pystray integration.""" 2 3import sys 4from unittest.mock import MagicMock, patch 5 6import pytest 7 8from i2p_apps.desktop.tray import TrayConfig, TrayMenu 9 10 11class TestTrayRunnerImport: 12 """Test that runner handles missing pystray gracefully.""" 13 14 def test_runner_raises_without_pystray(self): 15 """TrayRunner should raise RuntimeError if pystray is not installed.""" 16 with patch.dict(sys.modules, {"pystray": None, "PIL": None, "PIL.Image": None}): 17 # Force re-import with pystray unavailable 18 import importlib 19 import i2p_apps.desktop.runner as runner_mod 20 21 # Patch the module-level flag 22 orig = runner_mod._HAS_PYSTRAY 23 runner_mod._HAS_PYSTRAY = False 24 try: 25 with pytest.raises(RuntimeError, match="pystray is required"): 26 runner_mod.TrayRunner() 27 finally: 28 runner_mod._HAS_PYSTRAY = orig 29 30 31class TestTrayRunnerActions: 32 """Test action dispatch (mocked pystray).""" 33 34 def _make_runner(self): 35 """Create a TrayRunner with pystray mocked.""" 36 from i2p_apps.desktop.runner import TrayRunner 37 38 # If pystray is not installed, mock the flag 39 import i2p_apps.desktop.runner as mod 40 41 orig = mod._HAS_PYSTRAY 42 mod._HAS_PYSTRAY = True 43 try: 44 runner = object.__new__(TrayRunner) 45 runner._config = TrayConfig() 46 runner._menu = TrayMenu() 47 runner._icon = MagicMock() 48 return runner 49 finally: 50 mod._HAS_PYSTRAY = orig 51 52 def test_open_console_action(self): 53 """open_console should call webbrowser.open.""" 54 runner = self._make_runner() 55 with patch("i2p_apps.desktop.runner.webbrowser") as mock_wb: 56 runner._dispatch_action("open_console") 57 mock_wb.open.assert_called_once_with("http://127.0.0.1:7657") 58 59 def test_quit_action_stops_icon(self): 60 """quit action should stop the tray icon.""" 61 runner = self._make_runner() 62 runner._dispatch_action("quit") 63 runner._icon.stop.assert_called_once() 64 65 def test_shutdown_signals_router(self): 66 """shutdown_graceful should POST to router API.""" 67 runner = self._make_runner() 68 with patch("i2p_apps.desktop.runner.TrayRunner._signal_router") as mock_sig: 69 runner._dispatch_action("shutdown_graceful") 70 mock_sig.assert_called_once_with("shutdown") 71 72 def test_restart_signals_router(self): 73 """restart should POST to router API.""" 74 runner = self._make_runner() 75 with patch("i2p_apps.desktop.runner.TrayRunner._signal_router") as mock_sig: 76 runner._dispatch_action("restart") 77 mock_sig.assert_called_once_with("restart") 78 79 80class TestWindowsService: 81 """Test Windows service wrapper can be imported on all platforms.""" 82 83 def test_import_service_module(self): 84 """The service module should be importable even without pywin32.""" 85 from pathlib import Path 86 import importlib.util 87 88 # Find service.py relative to this test file's repo root 89 repo_root = Path(__file__).resolve().parents[2] 90 service_path = repo_root / "packaging" / "windows" / "service.py" 91 if not service_path.exists(): 92 pytest.skip("packaging/windows/service.py not found") 93 94 sys.path.insert(0, str(service_path.parent)) 95 try: 96 spec = importlib.util.spec_from_file_location("service", str(service_path)) 97 mod = importlib.util.module_from_spec(spec) 98 spec.loader.exec_module(mod) 99 100 # On non-Windows, _HAS_PYWIN32 should be False 101 assert hasattr(mod, "_HAS_PYWIN32") 102 assert hasattr(mod, "_run_router") 103 assert hasattr(mod, "cli") 104 finally: 105 sys.path.pop(0) 106 107 108class TestIconImage: 109 """Test icon image generation.""" 110 111 def test_create_icon_without_pystray(self): 112 """Should return None if pystray is not available.""" 113 from i2p_apps.desktop.runner import _create_icon_image 114 115 import i2p_apps.desktop.runner as mod 116 117 orig = mod._HAS_PYSTRAY 118 mod._HAS_PYSTRAY = False 119 try: 120 assert _create_icon_image() is None 121 finally: 122 mod._HAS_PYSTRAY = orig 123 124 125class TestMainEntryPoint: 126 """Test the main() entry point.""" 127 128 def test_main_exits_without_pystray(self, capsys): 129 """main() should print error and exit if pystray missing.""" 130 import i2p_apps.desktop.runner as mod 131 132 orig = mod._HAS_PYSTRAY 133 mod._HAS_PYSTRAY = False 134 try: 135 with pytest.raises(SystemExit) as exc_info: 136 mod.main() 137 assert exc_info.value.code == 1 138 finally: 139 mod._HAS_PYSTRAY = orig