# Apply the same SSL cert-store workaround the bot entry points use, so the # test suite can be collected on Windows machines whose cert store is corrupted. # Importing the bot code pulls in discord/aiohttp, which calls # ssl.create_default_context() at import time; a malformed Windows cert raises # ssl.SSLError: [ASN1: NOT_ENOUGH_DATA]. No-op on a healthy cert store (incl. CI). import sys if sys.platform == "win32": import ssl _orig_load_default_certs = ssl.SSLContext.load_default_certs def _safe_load_default_certs(self, purpose=ssl.Purpose.CLIENT_AUTH): try: _orig_load_default_certs(self, purpose) except ssl.SSLError: try: import certifi self.load_verify_locations(certifi.where()) except Exception: 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) # Tests must not post to the real Discord either. Config reads the webhook # from the repo's .env, and bot paths under test call # _save_error_screenshot -> messenger.send_error: on 2026-09-15 a town-manager # test sent a real "maintenance failed - Buy consumables failed (vendor not # found)" alert while the live bot was fine. from config import Config as _Config from messages import discord_embeds as _discord_embeds, generic_api as _generic_api _Config().general["custom_message_hook"] = "" _Config().general["custom_loot_message_hook"] = "" _Config().general["message_api_type"] = "" class _NoWebhook: @staticmethod def from_url(*args, **kwargs): raise RuntimeError("Discord disabled under pytest") _discord_embeds.SyncWebhook = _NoWebhook _generic_api.requests = type("_NoRequests", (), {"post": staticmethod(lambda *a, **k: None)})() # 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)