nanobot/tests/extensions/test_runtime.py

120 lines
3.3 KiB
Python

from pathlib import Path
import pytest
from nanobot.agent.tools.registry import ToolRegistry
from nanobot.command.router import CommandRouter
from nanobot.config.schema import Config
from nanobot.extensions import (
ExtensionCandidate,
ExtensionManifest,
ExtensionRuntime,
ExtensionRuntimeManager,
ExtensionScope,
ExtensionSnapshot,
)
def _snapshot(candidate: ExtensionCandidate) -> ExtensionSnapshot:
return ExtensionSnapshot((candidate,), (), ())
@pytest.mark.asyncio
async def test_runtime_activation_and_close_are_transactional(tmp_path: Path) -> None:
(tmp_path / "index.mjs").write_text(
"""
export default function (pi) {
pi.registerTool({
name: "remote_echo",
description: "Echo",
parameters: { type: "object", properties: {} },
execute: async () => ({ content: [{ type: "text", text: "ok" }] })
});
pi.registerCommand("remote", {
description: "Remote command",
handler: async () => undefined
});
pi.on("agent_start", () => undefined);
}
"""
)
candidate = ExtensionCandidate(
ExtensionManifest(
id="test.remote",
name="Remote",
version="1.0.0",
runtime=ExtensionRuntime.PI,
entry="index.mjs",
),
ExtensionScope.USER,
location=tmp_path,
trusted=True,
)
tools = ToolRegistry()
commands = CommandRouter()
manager = ExtensionRuntimeManager(
tools=tools,
commands=commands,
config=Config(),
)
result = await manager.activate(_snapshot(candidate))
assert not result.diagnostics
assert tools.owner("remote_echo") == "test.remote"
assert commands.owner("exact", "/remote") == "test.remote"
assert len(result.hook_factories) == 1
await manager.close()
assert tools.owner("remote_echo") is None
assert commands.owner("exact", "/remote") is None
@pytest.mark.asyncio
async def test_runtime_rolls_back_partial_registration(tmp_path: Path) -> None:
(tmp_path / "index.mjs").write_text(
"""
export default function (pi) {
pi.registerTool({
name: "duplicate",
description: "Duplicate",
parameters: {},
execute: async () => ({ content: [] })
});
pi.registerCommand("duplicate", {
handler: async () => undefined
});
}
"""
)
candidate = ExtensionCandidate(
ExtensionManifest(
id="test.duplicate",
name="Duplicate",
version="1.0.0",
runtime=ExtensionRuntime.PI,
entry="index.mjs",
),
ExtensionScope.USER,
location=tmp_path,
trusted=True,
)
commands = CommandRouter()
async def core_handler(_ctx):
return None
commands.exact("/duplicate", core_handler)
manager = ExtensionRuntimeManager(
tools=ToolRegistry(),
commands=commands,
config=Config(),
)
result = await manager.activate(_snapshot(candidate))
assert result.extensions == ()
assert result.diagnostics[0].code == "activation_failed"
assert commands.owner("exact", "/duplicate") == "nanobot.core"