bar: $nalej invents cocktails, $menu keeps the bar's growing lore
CI / compile (pull_request) Successful in 10s
CI / unit (pull_request) Successful in 16s
CI / integration (pull_request) Successful in 11s
build / build (push) Successful in 42s
CI / compile (push) Successful in 9s
CI / unit (push) Successful in 14s
CI / integration (push) Successful in 11s
CI / compile (pull_request) Successful in 10s
CI / unit (pull_request) Successful in 16s
CI / integration (pull_request) Successful in 11s
build / build (push) Successful in 42s
CI / compile (push) Successful in 9s
CI / unit (push) Successful in 14s
CI / integration (push) Successful in 11s
The most in-character capability the bot has: the persona is literally a 200kg bartender who mixes strong drinks with intriguing names. New always-loaded cog bar_commands: * $nalej [motyw] asks the ACTIVE AI backend (whatever $gadaj_teraz selects) to invent one themed cocktail in Conjurer's voice - persona reused from GPT_SETTINGS[0] as a system message, instructions as the user turn, via handle_response request_type NONE so it never pollutes the bar's conversation memory. Empty motyw = a surprise; "radio"/"pod muzykę" themes the drink on the track currently playing (PREPPED_TRACKS["now_playing"]). * every drink is appended to menu.json (new seeded state file, CONJURER_MENU_FILE overridable) with name/theme/author/timestamp/full text - emergent bar lore. * $menu lists the invented drinks and pours one at random from the archive. Text-only for now; a DALL-E drink image is an easy follow-up (the render path already exists in ai_commands, OpenAI-only). constants gains MENU_FILE (next to pamiec.json by default) + its seed; bot.py registers bar_commands as a core cog. Verified: tests/unit/test_bar_commands.py covers name extraction (markers/markdown/fallback) and the menu round-trip incl. corrupt-file tolerance; unit job 32 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #5.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""Unit tests for bar_commands: drink-name parsing and menu persistence.
|
||||
|
||||
The unit CI job installs only pytest, so bar_commands' heavy imports (discord,
|
||||
ai_functions -> openai/tiktoken, communication_subroutine -> flask) are stubbed
|
||||
only when genuinely absent - locally the real packages are used.
|
||||
"""
|
||||
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 - mirrors discord.Object
|
||||
self.id = id
|
||||
|
||||
mod.Object = Object
|
||||
ext = types.ModuleType("discord.ext")
|
||||
commands = types.ModuleType("discord.ext.commands")
|
||||
|
||||
class Cog:
|
||||
pass
|
||||
|
||||
def hybrid_command(**_kwargs):
|
||||
return lambda fn: fn
|
||||
|
||||
commands.Cog = Cog
|
||||
commands.hybrid_command = hybrid_command
|
||||
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", [])
|
||||
|
||||
mod.handle_response = handle_response
|
||||
return mod
|
||||
|
||||
|
||||
def _build_comm():
|
||||
mod = types.ModuleType("communication_subroutine")
|
||||
mod.PREPPED_TRACKS = {"now_playing": ""}
|
||||
return mod
|
||||
|
||||
|
||||
_stub_if_missing("discord", _build_discord)
|
||||
_stub_if_missing("ai_functions", _build_ai_functions)
|
||||
_stub_if_missing("communication_subroutine", _build_comm)
|
||||
|
||||
import bar_commands # noqa: E402 (after stubbing)
|
||||
|
||||
|
||||
def test_extract_name_strips_markers_and_markdown():
|
||||
assert bar_commands._extract_name("🍸 Krew Wilka\nskładniki...") == "Krew Wilka"
|
||||
assert bar_commands._extract_name("**Miód Odyna**\nfoo") == "Miód Odyna"
|
||||
assert bar_commands._extract_name("1. Cichy Grom\nbar") == "Cichy Grom"
|
||||
assert bar_commands._extract_name("\n\n Ostatni Skald \nx") == "Ostatni Skald"
|
||||
|
||||
|
||||
def test_extract_name_fallback_on_empty():
|
||||
assert bar_commands._extract_name("") == "Bezimienny drink"
|
||||
assert bar_commands._extract_name("\n \n") == "Bezimienny drink"
|
||||
|
||||
|
||||
def test_menu_round_trip(tmp_path, monkeypatch):
|
||||
menu_file = tmp_path / "menu.json"
|
||||
monkeypatch.setattr(bar_commands, "MENU_FILE", str(menu_file))
|
||||
|
||||
assert bar_commands._load_menu() == [] # missing file -> empty
|
||||
|
||||
bar_commands._append_menu({"nazwa": "Krew Wilka", "autor": "Hammer"})
|
||||
bar_commands._append_menu({"nazwa": "Miód Odyna", "autor": "Siara"})
|
||||
|
||||
menu = bar_commands._load_menu()
|
||||
assert [d["nazwa"] for d in menu] == ["Krew Wilka", "Miód Odyna"]
|
||||
|
||||
|
||||
def test_load_menu_survives_corrupt_file(tmp_path, monkeypatch):
|
||||
menu_file = tmp_path / "menu.json"
|
||||
menu_file.write_text("{ not json", encoding="utf-8")
|
||||
monkeypatch.setattr(bar_commands, "MENU_FILE", str(menu_file))
|
||||
assert bar_commands._load_menu() == []
|
||||
Reference in New Issue
Block a user