"""Two installations drive one EssentialCommands object graph under shared-runtime-v1. There is no full SDK worker harness for misc plugins, so this drives the real ``bind_invocation`` + ``InstallationBinding`` envelope against one process-wide plugin object and its real command components (the objects the shared runtime reuses for every installation). Assertions that fail on the pre-fix revision 0.1.9 (each was executed against it): * ``test_two_installations_render_their_own_language_from_one_object_graph`` -- the old ``initialize()`` copied ``self.config`` (empty under shared placement) into ``self.language``, so installation B rendered English: ``["Session reset"]`` instead of ``["会话已重置"]``; ``vars(plugin)`` also held the captured ``language`` key. * ``test_component_modules_do_not_mutate_sys_path_per_import`` -- every command module ran ``sys.path.insert(...)`` at import time, so importing the six command modules added six duplicate plugin-root entries (the package initializer now performs the single guarded insertion). """ from __future__ import annotations import asyncio import json import subprocess import sys import textwrap from pathlib import Path from types import SimpleNamespace import pytest from langbot_plugin.api.entities.builtin.command.context import ExecuteContext from langbot_plugin.api.entities.builtin.provider.session import LauncherTypes, Session from langbot_plugin.api.proxies.invocation import bind_invocation from langbot_plugin.entities.io.context import InstallationBinding PLUGIN_ROOT = Path(__file__).resolve().parents[1] if str(PLUGIN_ROOT) not in sys.path: sys.path.insert(0, str(PLUGIN_ROOT)) from components.commands.help import Help # noqa: E402 from components.commands.reset import Reset # noqa: E402 from main import EssentialCommands # noqa: E402 # Per-installation settings as the Host persists them. CONFIGS = {"a": {"language": "en_US"}, "b": {"language": "zh_Hans"}} EXPECTED = { "a": {"reset": "Session reset", "help_title": "LangBot - Production-grade IM bot development platform"}, "b": {"reset": "会话已重置", "help_title": "LangBot - 生产级 IM 机器人开发平台"}, } def _binding( installation: str, *, workspace: str | None = None, instance: str = "instance-1", revision: int = 1, ) -> InstallationBinding: return InstallationBinding( instance_uuid=instance, workspace_uuid=workspace or f"workspace-{installation}", installation_uuid=f"installation-{installation}", runtime_revision=revision, artifact_digest="a" * 64, ) def _shared_plugin(handler) -> EssentialCommands: """One process-wide plugin object, as the shared worker builds it.""" plugin = EssentialCommands() plugin.plugin_runtime_handler = handler plugin.config = {} # ATTACH binds an empty config for the shared object graph return plugin def _context(command: str) -> ExecuteContext: return ExecuteContext( query_id=1, session=Session(launcher_type=LauncherTypes.PERSON, launcher_id="10001"), command_text=command, full_command_text=f"!{command}", command=command, crt_command=command, params=[], crt_params=[], privilege=0, ) async def _render(component, command: str) -> list[str]: return [ret.text async for ret in component._execute(_context(command))] def test_two_installations_render_their_own_language_from_one_object_graph(): async def scenario(): handler = SimpleNamespace() plugin = _shared_plugin(handler) await plugin.initialize() # process-scoped; must capture no tenant language reset, help_cmd = Reset(), Help() for component in (reset, help_cmd): component.plugin = plugin await component.initialize() rendered = {} for owner in ("a", "b"): with bind_invocation(handler, config=CONFIGS[owner], binding=_binding(owner)): rendered[owner] = { "reset": (await _render(reset, "reset"))[0], "help_title": (await _render(help_cmd, "help"))[0].splitlines()[0], } return plugin, rendered plugin, rendered = asyncio.run(scenario()) assert rendered["a"] == EXPECTED["a"] assert rendered["b"] == EXPECTED["b"] # Nothing tenant-scoped was captured on the shared plugin object. assert set(vars(plugin)) == {"_legacy_config", "plugin_runtime_handler"} def test_component_modules_do_not_mutate_sys_path_per_import(): code = textwrap.dedent( f""" import json import sys root = {str(PLUGIN_ROOT)!r} before = sum(1 for entry in sys.path if entry == root) import components.commands.cmd import components.commands.func import components.commands.help import components.commands.plugin import components.commands.reset import components.commands.version from i18n import get_text after = sum(1 for entry in sys.path if entry == root) print(json.dumps({{"before": before, "after": after, "reset": get_text("zh_Hans", "reset.success")}})) """ ) result = subprocess.run( [sys.executable, "-c", code], cwd=str(PLUGIN_ROOT), capture_output=True, text=True, ) assert result.returncode == 0, result.stderr observed = json.loads(result.stdout.strip().splitlines()[-1]) # Exactly the package initializer may add the plugin root; the six command # modules used to add one entry each. assert observed["after"] - observed["before"] <= 1 assert observed["after"] >= 1 assert observed["reset"] == "会话已重置" def test_dedicated_worker_config_is_still_honoured_without_a_binding(): """Dedicated placement has no installation binding: the instance config applies.""" handler = SimpleNamespace() plugin = EssentialCommands() plugin.plugin_runtime_handler = handler plugin.config = {"language": "zh_Hans"} assert plugin.get_language() == "zh_Hans" with bind_invocation(handler, config={"language": "en_US"}, binding=_binding("a")): assert plugin.get_language() == "en_US" if __name__ == "__main__": raise SystemExit(pytest.main([__file__, "-q"]))