oracle: $runy / $runa_dnia - Elder Futhark rune readings in-character
CI / compile (pull_request) Successful in 10s
CI / unit (pull_request) Successful in 14s
CI / integration (pull_request) Successful in 10s
build / build (push) Successful in 38s
CI / compile (push) Successful in 10s
CI / unit (push) Successful in 17s
CI / integration (push) Successful in 11s

Fits the mythology pillar of the persona (Slavic/Norse/Celtic, Old Norse
phrases). New always-loaded cog oracle_commands:

* $runy [pytanie] draws three Elder Futhark runes (past/present/future, with
  upright/reversed orientation - the 8 symmetric runes are never reversed) and
  asks the ACTIVE AI backend to read the spread in Conjurer's voice. If the AI
  is down it still shows the drawn runes with their own meanings, so the command
  always answers.
* $runa_dnia gives one rune, deterministic per user per day (sha256 seed), so
  it's stable if asked repeatedly - no AI call, no state file.

The full 24-rune Futhark, the draw logic and the reversal rules are pure and
unit-tested (distinct draw, non-invertible never reversed, per-day stability,
meaning fallback).

Also fixes a pre-existing unit-job breakage: test_bar_commands and
test_lore_commands each stubbed `discord` with different completeness and
shared sys.modules, so once both landed on main the one lacking `discord.ext.tasks`
shadowed the one needing it and collection failed order-dependently. A new
tests/unit/conftest.py stubs discord once, completely, before any test module -
the per-file stubs then skip. Full unit job: 41 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #7.
This commit is contained in:
2026-07-31 20:38:48 +02:00
parent 3f4a1d5083
commit e1fca864d8
4 changed files with 339 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
"""Shared stubs for the unit tests.
Several bot modules under test import ``discord`` (and ``discord.ext.commands`` /
``discord.ext.tasks``) at import time. The pytest-only unit CI job doesn't
install discord, so the test modules stub it themselves - but each stubbed only
the pieces IT needed, and because they share ``sys.modules`` the first one to run
won (skip-if-present), leaving a later module that needs e.g. ``discord.ext.tasks``
with an incomplete stub. That made collection order-dependent and flaky (e.g.
test_bar_commands, whose stub omits ``tasks``, shadowing test_lore_commands,
which needs it).
Stubbing ``discord`` here - once, completely, before any test module is imported
- removes the ordering dependency. The per-file stubs then simply skip. Only
``discord`` is stubbed centrally; ai_functions / communication_subroutine stay
per-file because different modules legitimately want the real vs a stubbed one.
"""
import sys
import types
def _ensure_discord_stub() -> None:
if "discord" in sys.modules:
return
try: # real discord present (local dev) -> use it
import discord # noqa: F401
return
except ImportError:
pass
mod = types.ModuleType("discord")
class Object:
def __init__(self, id=None): # noqa: A002 - mirrors discord.Object
self.id = id
class Member:
pass
mod.Object = Object
mod.Member = Member
ext = types.ModuleType("discord.ext")
commands = types.ModuleType("discord.ext.commands")
tasks = types.ModuleType("discord.ext.tasks")
class Cog:
pass
commands.Cog = Cog
commands.hybrid_command = lambda **_kwargs: (lambda fn: fn)
def loop(**_kwargs):
def decorator(fn):
fn.before_loop = lambda f: f
return fn
return decorator
tasks.loop = loop
commands.tasks = tasks
ext.commands = commands
ext.tasks = tasks
mod.ext = ext
sys.modules["discord"] = mod
sys.modules["discord.ext"] = ext
sys.modules["discord.ext.commands"] = commands
sys.modules["discord.ext.tasks"] = tasks
_ensure_discord_stub()