feat: detect double-click launch on Windows and abort with info message (#570)

---------

Co-authored-by: Jens Bergmann <1742418+1cu@users.noreply.github.com>
This commit is contained in:
Sebastian Thomschke
2025-07-05 13:58:24 +02:00
committed by GitHub
parent 14a917a1c7
commit b7882065b7
4 changed files with 196 additions and 1 deletions

View File

@@ -0,0 +1,107 @@
# SPDX-FileCopyrightText: © Jens Bergmann and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
import builtins, importlib, sys # isort: skip
from unittest import mock
import pytest
from kleinanzeigen_bot.utils.i18n import Locale
# --- Platform-specific test for Windows double-click guard ---
@pytest.mark.parametrize(
("compiled_exe", "windows_double_click_launch", "expected_error_msg_lang"),
[
(True, True, "en"), # Windows Explorer double-click - English locale
(True, True, "de"), # Windows Explorer double-click - German locale
(True, False, None), # Windows Terminal launch - compiled exe
(False, False, None), # Windows Terminal launch - from source code
],
)
@pytest.mark.skipif(sys.platform != "win32", reason = "ctypes.windll only exists on Windows")
def test_guard_triggers_on_double_click_windows(
monkeypatch:pytest.MonkeyPatch,
capsys:pytest.CaptureFixture[str],
compiled_exe:bool,
windows_double_click_launch:bool | None,
expected_error_msg_lang:str | None
) -> None:
# Prevent blocking in tests
monkeypatch.setattr(builtins, "input", lambda: None)
# Simulate target platform
monkeypatch.setattr(sys, "platform", "win32")
# Simulate compiled executable
monkeypatch.setattr(
"kleinanzeigen_bot.utils.misc.is_frozen",
lambda: compiled_exe,
)
# Force specific locale
if expected_error_msg_lang:
monkeypatch.setattr(
"kleinanzeigen_bot.utils.i18n.get_current_locale",
lambda: Locale(expected_error_msg_lang),
)
# Spy on sys.exit
exit_mock = mock.Mock(wraps = sys.exit)
monkeypatch.setattr(sys, "exit", exit_mock)
# Simulate double-click launch on Windows
if windows_double_click_launch is not None:
pid_count = 2 if windows_double_click_launch else 3 # 2 -> Explorer, 3 -> Terminal
k32 = mock.Mock()
k32.GetConsoleProcessList.return_value = pid_count
monkeypatch.setattr("ctypes.windll.kernel32", k32)
# Reload module to pick up system monkeypatches
guard = importlib.reload(
importlib.import_module("kleinanzeigen_bot.utils.launch_mode_guard")
)
if expected_error_msg_lang:
with pytest.raises(SystemExit) as exc:
guard.ensure_not_launched_from_windows_explorer()
assert exc.value.code == 1
exit_mock.assert_called_once_with(1)
captured = capsys.readouterr()
if expected_error_msg_lang == "de":
assert "Du hast das Programm scheinbar per Doppelklick gestartet." in captured.err
else:
assert "It looks like you launched it by double-clicking the EXE." in captured.err
assert not captured.out # nothing to stdout
else:
guard.ensure_not_launched_from_windows_explorer()
exit_mock.assert_not_called()
captured = capsys.readouterr()
assert not captured.err # nothing to stderr
# --- Platform-agnostic tests for non-Windows and non-frozen code paths ---
@pytest.mark.parametrize(
("platform", "compiled_exe"),
[
("linux", True),
("linux", False),
("darwin", True),
("darwin", False),
],
)
def test_guard_non_windows_and_non_frozen(
monkeypatch:pytest.MonkeyPatch,
platform:str,
compiled_exe:bool
) -> None:
monkeypatch.setattr(sys, "platform", platform)
monkeypatch.setattr("kleinanzeigen_bot.utils.misc.is_frozen", lambda: compiled_exe)
# Reload module to pick up system monkeypatches
guard = importlib.reload(
importlib.import_module("kleinanzeigen_bot.utils.launch_mode_guard")
)
# Should not raise or print anything
guard.ensure_not_launched_from_windows_explorer()