Files
my-botty/test/conftest.py
T
alexpolo1andClaude Opus 5 69edbf8998 test: never post to the real Discord webhook during pytest
Bot paths under test call _save_error_screenshot -> messenger.send_error, and
Config reads the real webhook from the repo's .env. On 2026-09-15 a test run
sent a real 'maintenance failed - Buy consumables failed (vendor not found)'
alert while the live bot was completing every game. conftest now blanks the
message hooks and disables SyncWebhook/generic_api posting.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-15 18:31:38 +02:00

81 lines
3.5 KiB
Python

# 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)