nanobot/tests/extensions/test_registry.py

142 lines
3.7 KiB
Python

from nanobot.extensions import (
ContributionKind,
ExtensionCandidate,
ExtensionContribution,
ExtensionManifest,
ExtensionPolicy,
ExtensionRegistry,
ExtensionRuntime,
ExtensionScope,
)
def _candidate(
extension_id: str,
*,
scope: ExtensionScope,
contribution_name: str = "",
replaces: tuple[str, ...] = (),
trusted: bool = True,
) -> ExtensionCandidate:
contributions = (
ExtensionContribution(
kind=ContributionKind.TOOL,
name=contribution_name,
replaces=replaces,
),
) if contribution_name else ()
return ExtensionCandidate(
manifest=ExtensionManifest(
id=extension_id,
name=extension_id,
version="1.0.0",
runtime=ExtensionRuntime.PYTHON,
contributions=contributions,
),
scope=scope,
trusted=trusted,
)
def test_workspace_copy_shadows_user_and_builtin_copy_of_same_extension() -> None:
registry = ExtensionRegistry()
registry.register(_candidate("acme", scope=ExtensionScope.BUILTIN))
registry.register(_candidate("acme", scope=ExtensionScope.USER))
registry.register(_candidate("acme", scope=ExtensionScope.WORKSPACE))
snapshot = registry.snapshot()
assert len(snapshot.extensions) == 1
assert snapshot.extensions[0].scope is ExtensionScope.WORKSPACE
def test_policy_filters_extensions_before_contribution_resolution() -> None:
registry = ExtensionRegistry(
ExtensionPolicy(allow=frozenset({"allowed"}), deny=frozenset())
)
registry.register(
_candidate(
"allowed",
scope=ExtensionScope.USER,
contribution_name="allowed_tool",
)
)
registry.register(
_candidate(
"hidden",
scope=ExtensionScope.USER,
contribution_name="hidden_tool",
)
)
snapshot = registry.snapshot()
assert [extension.manifest.id for extension in snapshot.extensions] == ["allowed"]
assert [
contribution.contribution.name for contribution in snapshot.contributions
] == ["allowed_tool"]
def test_untrusted_external_extension_is_visible_to_discovery_but_not_active() -> None:
registry = ExtensionRegistry()
registry.register(
_candidate(
"untrusted",
scope=ExtensionScope.USER,
contribution_name="unsafe_tool",
trusted=False,
)
)
snapshot = registry.snapshot()
assert snapshot.extensions == ()
assert snapshot.contributions == ()
def test_conflicting_contribution_does_not_silently_replace_owner() -> None:
registry = ExtensionRegistry()
registry.register(
_candidate(
"core",
scope=ExtensionScope.BUILTIN,
contribution_name="shell",
)
)
registry.register(
_candidate(
"third-party",
scope=ExtensionScope.WORKSPACE,
contribution_name="shell",
)
)
snapshot = registry.snapshot()
assert snapshot.contributions[0].owner.manifest.id == "core"
assert snapshot.diagnostics[0].code == "contribution_conflict"
def test_explicit_higher_scope_replacement_takes_ownership() -> None:
registry = ExtensionRegistry()
registry.register(
_candidate(
"core",
scope=ExtensionScope.BUILTIN,
contribution_name="shell",
)
)
registry.register(
_candidate(
"replacement",
scope=ExtensionScope.WORKSPACE,
contribution_name="shell",
replaces=("core",),
)
)
snapshot = registry.snapshot()
assert snapshot.contributions[0].owner.manifest.id == "replacement"
assert snapshot.diagnostics == ()