mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-21 20:06:06 +00:00
115 lines
3.7 KiB
Python
115 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from langbot.pkg.api.http.authz import WorkspaceRequiredError
|
|
from langbot.pkg.api.http.context import ExecutionContext
|
|
from langbot.pkg.platform.botmgr import PlatformManager, RuntimeBot
|
|
|
|
|
|
WORKSPACE_A = '00000000-0000-0000-0000-00000000000a'
|
|
WORKSPACE_B = '00000000-0000-0000-0000-00000000000b'
|
|
BOT_A = '10000000-0000-0000-0000-00000000000a'
|
|
BOT_B = '10000000-0000-0000-0000-00000000000b'
|
|
|
|
|
|
def _context(workspace_uuid: str, bot_uuid: str, generation: int = 4) -> ExecutionContext:
|
|
return ExecutionContext(
|
|
instance_uuid='instance',
|
|
workspace_uuid=workspace_uuid,
|
|
placement_generation=generation,
|
|
bot_uuid=bot_uuid,
|
|
)
|
|
|
|
|
|
def _runtime(application, workspace_uuid: str, bot_uuid: str) -> RuntimeBot:
|
|
entity = SimpleNamespace(
|
|
uuid=bot_uuid,
|
|
workspace_uuid=workspace_uuid,
|
|
name='Same Name',
|
|
enable=True,
|
|
pipeline_routing_rules=[],
|
|
use_pipeline_uuid=None,
|
|
)
|
|
return RuntimeBot(
|
|
ap=application,
|
|
bot_entity=entity,
|
|
adapter=SimpleNamespace(),
|
|
logger=SimpleNamespace(),
|
|
execution_context=_context(workspace_uuid, bot_uuid),
|
|
)
|
|
|
|
|
|
class _WorkspaceService:
|
|
async def get_execution_binding(self, workspace_uuid, expected_generation=None):
|
|
if workspace_uuid not in {WORKSPACE_A, WORKSPACE_B} or expected_generation != 4:
|
|
raise ValueError('stale')
|
|
return SimpleNamespace(
|
|
instance_uuid='instance',
|
|
workspace_uuid=workspace_uuid,
|
|
placement_generation=4,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def manager():
|
|
application = SimpleNamespace(workspace_service=_WorkspaceService())
|
|
platform_manager = PlatformManager(application)
|
|
platform_manager.bots = [
|
|
_runtime(application, WORKSPACE_A, BOT_A),
|
|
_runtime(application, WORKSPACE_B, BOT_B),
|
|
]
|
|
return platform_manager
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_runtime_lookup_cannot_guess_another_workspace_bot(manager):
|
|
assert await manager.get_bot_by_uuid(_context(WORKSPACE_A, BOT_A), BOT_A) is manager.bots[0]
|
|
assert await manager.get_bot_by_uuid(_context(WORKSPACE_B, BOT_A), BOT_A) is None
|
|
assert await manager.get_bot_by_uuid(_context(WORKSPACE_A, BOT_B), BOT_B) is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_public_route_key_resolves_bound_runtime_and_rejects_non_opaque_input(manager):
|
|
assert await manager.resolve_public_bot(BOT_A) is manager.bots[0]
|
|
assert await manager.resolve_public_bot('Same Name') is None
|
|
assert await manager.resolve_public_bot('not-a-uuid') is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stale_runtime_generation_is_not_returned(manager):
|
|
assert await manager.get_bot_by_uuid(_context(WORKSPACE_A, BOT_A, generation=5), BOT_A) is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_runtime_bot_revalidates_its_generation_before_handling_events(manager):
|
|
runtime_bot = manager.bots[0]
|
|
|
|
await runtime_bot.assert_execution_active()
|
|
|
|
runtime_bot.placement_generation = 5
|
|
with pytest.raises(ValueError, match='stale'):
|
|
await runtime_bot.assert_execution_active()
|
|
|
|
|
|
def test_runtime_bot_rejects_workspace_mismatch():
|
|
application = SimpleNamespace()
|
|
entity = SimpleNamespace(
|
|
uuid=BOT_A,
|
|
workspace_uuid=WORKSPACE_A,
|
|
name='Bot',
|
|
enable=True,
|
|
pipeline_routing_rules=[],
|
|
use_pipeline_uuid=None,
|
|
)
|
|
with pytest.raises(WorkspaceRequiredError):
|
|
RuntimeBot(
|
|
ap=application,
|
|
bot_entity=entity,
|
|
adapter=SimpleNamespace(),
|
|
logger=SimpleNamespace(),
|
|
execution_context=_context(WORKSPACE_B, BOT_A),
|
|
)
|