Build reusable bot framework
This commit is contained in:
156
tests/unit/test_registry.py
Normal file
156
tests/unit/test_registry.py
Normal file
@@ -0,0 +1,156 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import core.registry as registry_module
|
||||
from core.registry import FrameworkRegistry
|
||||
|
||||
|
||||
PROMPT = {"system": "Return JSON", "user_template": "Message: {user_input}"}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clean_global_registry():
|
||||
registry_module.reset_registry()
|
||||
yield
|
||||
registry_module.reset_registry()
|
||||
|
||||
|
||||
def test_discovery_is_alphabetical_ignores_private_packages_and_runs_once(monkeypatch):
|
||||
loaded = []
|
||||
|
||||
def feature(name):
|
||||
def register(target):
|
||||
loaded.append(name)
|
||||
target.describe(f"{name} feature")
|
||||
target.register_command(
|
||||
name,
|
||||
lambda _context, _parsed: None,
|
||||
PROMPT,
|
||||
description=f"Handle {name}",
|
||||
help_text=[f"use {name}"],
|
||||
)
|
||||
|
||||
return SimpleNamespace(register=register)
|
||||
|
||||
fake_modules = {
|
||||
"modules.alpha": feature("alpha"),
|
||||
"modules.zeta": feature("zeta"),
|
||||
}
|
||||
|
||||
def fake_import(name):
|
||||
if name == "modules":
|
||||
return SimpleNamespace(__path__=["unused"])
|
||||
return fake_modules[name]
|
||||
|
||||
discovered = [
|
||||
SimpleNamespace(name="zeta", ispkg=True),
|
||||
SimpleNamespace(name="_private", ispkg=True),
|
||||
SimpleNamespace(name="single_file", ispkg=False),
|
||||
SimpleNamespace(name="alpha", ispkg=True),
|
||||
]
|
||||
monkeypatch.setattr(registry_module.importlib, "import_module", fake_import)
|
||||
monkeypatch.setattr(
|
||||
registry_module.pkgutil, "iter_modules", lambda _path: discovered
|
||||
)
|
||||
|
||||
result = registry_module.discover_modules()
|
||||
|
||||
assert loaded == ["alpha", "zeta"]
|
||||
assert list(result.modules) == ["alpha", "zeta"]
|
||||
assert result.list_commands() == ["alpha", "zeta"]
|
||||
assert registry_module.discover_modules() is result
|
||||
assert loaded == ["alpha", "zeta"]
|
||||
|
||||
|
||||
def test_duplicate_names_and_malformed_registrations_are_rejected():
|
||||
target = FrameworkRegistry()
|
||||
handler = lambda _context, _parsed: None
|
||||
|
||||
target.begin_module("first", "modules.first")
|
||||
target.register_command("shared", handler, PROMPT)
|
||||
target.register_job("shared.job", handler)
|
||||
target.finish_module()
|
||||
|
||||
with pytest.raises(ValueError, match="Duplicate module name: first"):
|
||||
target.begin_module("first", "modules.again")
|
||||
|
||||
target.begin_module("second", "modules.second")
|
||||
with pytest.raises(ValueError, match="Duplicate command type: shared"):
|
||||
target.register_command("shared", handler, PROMPT)
|
||||
with pytest.raises(ValueError, match="Duplicate job type: shared.job"):
|
||||
target.register_job("shared.job", handler)
|
||||
with pytest.raises(TypeError, match="must be callable"):
|
||||
target.register_command("not_callable", None, PROMPT)
|
||||
with pytest.raises(TypeError, match="Validator .* must be callable"):
|
||||
target.register_command("bad_validator", handler, PROMPT, validator="bad")
|
||||
with pytest.raises(ValueError, match="system and user_template"):
|
||||
target.register_command("bad_prompt", handler, {"system": "only one"})
|
||||
target.finish_module()
|
||||
|
||||
with pytest.raises(RuntimeError, match="inside a module register"):
|
||||
target.describe("orphan metadata")
|
||||
|
||||
|
||||
def test_help_and_router_context_are_generated_from_sorted_metadata():
|
||||
target = FrameworkRegistry()
|
||||
handler = lambda _context, _parsed: None
|
||||
target.begin_module("examples", "modules.examples")
|
||||
target.register_command(
|
||||
"zeta",
|
||||
handler,
|
||||
PROMPT,
|
||||
description="Last command",
|
||||
)
|
||||
target.register_command(
|
||||
"alpha",
|
||||
handler,
|
||||
PROMPT,
|
||||
description="First command",
|
||||
help_text=["say alpha", "ask alpha for help"],
|
||||
)
|
||||
target.finish_module()
|
||||
|
||||
assert target.router_context() == (
|
||||
"- alpha: First command\n- zeta: Last command"
|
||||
)
|
||||
assert target.help_lines() == [
|
||||
"- say alpha",
|
||||
"- ask alpha for help",
|
||||
"- zeta: Last command",
|
||||
]
|
||||
|
||||
|
||||
def test_failed_forced_discovery_clears_partial_state_and_can_retry(monkeypatch):
|
||||
broken = SimpleNamespace(register=lambda target: target.describe("partial"))
|
||||
monkeypatch.setattr(
|
||||
registry_module.importlib,
|
||||
"import_module",
|
||||
lambda name: (
|
||||
SimpleNamespace(__path__=["unused"]) if name == "modules" else broken
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
registry_module.pkgutil,
|
||||
"iter_modules",
|
||||
lambda _path: [SimpleNamespace(name="broken", ispkg=True)],
|
||||
)
|
||||
|
||||
registry_module.discover_modules()
|
||||
assert registry_module.registry.modules == {
|
||||
"broken": {
|
||||
"name": "broken",
|
||||
"package": "modules.broken",
|
||||
"description": "partial",
|
||||
}
|
||||
}
|
||||
|
||||
del broken.register
|
||||
with pytest.raises(RuntimeError, match="must expose register"):
|
||||
registry_module.discover_modules(force=True)
|
||||
assert registry_module.registry.modules == {}
|
||||
|
||||
broken.register = lambda target: target.describe("recovered")
|
||||
assert registry_module.discover_modules().modules["broken"][
|
||||
"description"
|
||||
] == "recovered"
|
||||
Reference in New Issue
Block a user