diff --git a/test/conftest.py b/test/conftest.py index ff51b18..213dc5b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -21,3 +21,41 @@ if sys.platform == "win32": pass ssl.SSLContext.load_default_certs = _safe_load_default_certs + + +# Never let a test drive the real desktop. +# +# Many tests run real bot code paths (town maintenance, waypoints, NPC clicks, game +# creation) with only the screen faked. Those paths end in win_input's SendInput / +# SetCursorPos, which act on whatever window has focus. On 2026-09-15 the suite was +# run repeatedly while the bot was playing: D2R received test keystrokes and clicks - +# a game started with no bot input, the inventory opened, and the character ended up +# stranded - and the bot was blamed for "getting stuck". +# +# Block it at the lowest layer so no test can reach the OS, whatever it forgets to +# mock. Set BOTTY_TESTS_ALLOW_REAL_INPUT=1 to opt out for a deliberate live test. +if sys.platform == "win32": + import os + + if not os.environ.get("BOTTY_TESTS_ALLOW_REAL_INPUT"): + + class _NoRealInputUser32: + """user32 proxy: input and cursor calls report success and do nothing.""" + + def __init__(self, real): + self._real = real + + def __getattr__(self, name): + if name == "SendInput": + return lambda count, *args: count # "all events inserted" + if name in ("SetCursorPos", "keybd_event", "mouse_event"): + return lambda *args: 1 + return getattr(self._real, name) + + from input_layer import win_input as _win_input + from utils import misc as _misc + + _win_input.user32 = _NoRealInputUser32(_win_input.user32) + # Window moves / focus steals: tests must not reposition or front D2R either. + for _name in ("SetWindowPos", "SetForegroundWindow", "ShowWindow"): + setattr(_misc, _name, lambda *args, **kwargs: None)