"""Unit tests for oracle_commands' pure rune-draw logic. Heavy imports (discord, ai_functions) are stubbed only when genuinely absent, matching the pytest-only unit CI job. """ import random import sys import types def _stub_if_missing(name, build): if name in sys.modules: return try: __import__(name) except ImportError: sys.modules[name] = build() def _build_discord(): mod = types.ModuleType("discord") class Object: def __init__(self, id=None): # noqa: A002 self.id = id mod.Object = Object ext = types.ModuleType("discord.ext") commands = types.ModuleType("discord.ext.commands") class Cog: pass commands.Cog = Cog commands.hybrid_command = lambda **_k: (lambda fn: fn) ext.commands = commands mod.ext = ext sys.modules["discord.ext"] = ext sys.modules["discord.ext.commands"] = commands return mod def _build_ai_functions(): mod = types.ModuleType("ai_functions") async def handle_response(*_a, **_k): return ("stub reading", []) mod.handle_response = handle_response return mod _stub_if_missing("discord", _build_discord) _stub_if_missing("ai_functions", _build_ai_functions) import oracle_commands # noqa: E402 (after stubbing) def test_full_elder_futhark_present(): assert len(oracle_commands.RUNES) == 24 # every rune has the required fields for r in oracle_commands.RUNES: assert {"symbol", "nazwa", "znaczenie", "odwrocone"} <= set(r) def test_draw_returns_distinct_runes(): drawn = oracle_commands.draw_runes(3, random.Random(1)) names = [r["nazwa"] for r, _ in drawn] assert len(names) == 3 assert len(set(names)) == 3 # distinct def test_non_invertible_runes_are_never_reversed(): # Run many draws of the whole deck; any rune with empty 'odwrocone' must # never come up reversed. for seed in range(200): for rune, reversed_ in oracle_commands.draw_runes(24, random.Random(seed)): if not rune["odwrocone"]: assert reversed_ is False, f"{rune['nazwa']} was reversed but is non-invertible" def test_runa_dnia_is_stable_per_user_per_day(): # Same user + same day -> identical draw; different day -> RNG differs. r1 = oracle_commands.draw_runes(1, oracle_commands._rng_for_day(42, "2026-07-31"))[0] r2 = oracle_commands.draw_runes(1, oracle_commands._rng_for_day(42, "2026-07-31"))[0] assert r1[0]["nazwa"] == r2[0]["nazwa"] and r1[1] == r2[1] seeds = { oracle_commands._rng_for_day(uid, "2026-07-31").random() for uid in range(50) } assert len(seeds) > 1 # different users get different streams def test_meaning_falls_back_to_upright_for_non_invertible(): gebo = next(r for r in oracle_commands.RUNES if r["nazwa"] == "Gebo") # even if asked "reversed", a non-invertible rune yields its upright meaning assert oracle_commands._meaning(gebo, True) == gebo["znaczenie"]