fix(box): align integration tests with runner-owned bindings

This commit is contained in:
RockChinQ
2026-09-18 01:07:43 +08:00
parent a6ac25cf84
commit e77acfa3ab
5 changed files with 89 additions and 26 deletions
+18 -4
View File
@@ -670,7 +670,7 @@ async def test_box_service_allows_host_mount_under_configured_root(tmp_path):
'cmd': 'pwd',
'host_path': str(host_dir),
'host_path_mode': BoxHostMountMode.READ_WRITE.value,
'session_id': '11',
'session_id': 'person_test_user',
},
make_query(11),
)
@@ -962,7 +962,7 @@ async def test_profile_unlocked_field_can_be_overridden():
await service.initialize()
result = await service.execute_spec_payload(
{'cmd': 'echo hi', 'timeout_sec': 60, 'network': 'on', 'session_id': '31'},
{'cmd': 'echo hi', 'timeout_sec': 60, 'network': 'on', 'session_id': 'person_test_user'},
make_query(31),
)
@@ -984,7 +984,7 @@ async def test_profile_locked_field_cannot_be_overridden():
await service.initialize()
result = await service.execute_spec_payload(
{'cmd': 'echo hi', 'network': 'on', 'host_path_mode': 'rw', 'session_id': '32'},
{'cmd': 'echo hi', 'network': 'on', 'host_path_mode': 'rw', 'session_id': 'person_test_user'},
make_query(32),
)
@@ -1193,7 +1193,7 @@ async def test_profile_offline_readonly_locks_read_only_rootfs():
await service.initialize()
await service.execute_spec_payload(
{'cmd': 'echo hi', 'read_only_rootfs': False, 'session_id': '41'}, make_query(41)
{'cmd': 'echo hi', 'read_only_rootfs': False, 'session_id': 'person_test_user'}, make_query(41)
)
spec = backend.start_specs[0]
@@ -2372,3 +2372,17 @@ class TestAttachmentHostPath:
service.default_workspace = None
# Must not raise.
await service._purge_attachment_dirs()
@pytest.mark.asyncio
async def test_execution_cannot_override_runner_bound_session():
logger = Mock()
backend = FakeBackend(logger)
runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300)
service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime))
await service.initialize()
with pytest.raises(BoxValidationError, match='session_id must match the bound Box'):
await service.execute_spec_payload({'cmd': 'true', 'session_id': 'other-box'}, make_query())
assert backend.start_calls == []
+27 -7
View File
@@ -8,6 +8,7 @@ from unittest.mock import AsyncMock, Mock
import pytest
from langbot.pkg.api.http.context import ExecutionContext
from langbot.pkg.box.runner import RunBoxBinding
_CONTEXT = ExecutionContext(
@@ -30,6 +31,12 @@ def _make_query(*, variables=None, **kwargs):
)
def _make_bound_query(**kwargs):
query = _make_query(**kwargs)
query._box_binding = RunBoxBinding('run-a', 'skill-box', {}, 'run-a')
return query
def _make_skill_manager(skills: dict[str, dict], **kwargs):
return SimpleNamespace(
skills=skills,
@@ -546,14 +553,14 @@ class TestNativeToolLoaderSkillPaths:
await loader.invoke_tool(
'read',
{'path': '/workspace/.skills/demo/SKILL.md'},
_make_query(query_id='q1', variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']}),
_make_bound_query(query_id='q1', variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']}),
)
return
result = await loader.invoke_tool(
'read',
{'path': '/workspace/.skills/demo/SKILL.md'},
_make_query(query_id='q1', variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']}),
_make_bound_query(query_id='q1', variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']}),
)
assert result['ok'] is True
@@ -577,7 +584,7 @@ class TestNativeToolLoaderSkillPaths:
)
ap.skill_mgr = _make_skill_manager({'demo': _make_skill_data(name='demo', package_root=tmpdir)})
loader = NativeToolLoader(ap)
query = _make_query(
query = _make_bound_query(
query_id='q-external-read',
variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']},
)
@@ -609,7 +616,7 @@ class TestNativeToolLoaderSkillPaths:
)
ap.skill_mgr = _make_skill_manager({'demo': _make_skill_data(name='demo', package_root=tmpdir)})
loader = NativeToolLoader(ap)
query = _make_query(
query = _make_bound_query(
query_id='q-external-no-protocol',
variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']},
)
@@ -639,7 +646,7 @@ class TestNativeToolLoaderSkillPaths:
ap.skill_mgr = SimpleNamespace(refresh_skill_from_disk=Mock())
loader = NativeToolLoader(ap)
query = _make_query(query_id='q1', launcher_type='person', launcher_id='123')
query = _make_bound_query(query_id='q1', launcher_type='person', launcher_id='123')
register_activated_skill(query, _make_skill_data(name='demo', package_root=tmpdir))
result = await loader.invoke_tool(
@@ -671,7 +678,7 @@ class TestNativeToolLoaderSkillPaths:
)
ap.skill_mgr = SimpleNamespace(refresh_skill_from_disk=Mock())
loader = NativeToolLoader(ap)
query = _make_query(query_id='q-external', launcher_type='person', launcher_id='123')
query = _make_bound_query(query_id='q-external', launcher_type='person', launcher_id='123')
register_activated_skill(
query,
_make_skill_data(
@@ -709,7 +716,7 @@ class TestNativeToolLoaderSkillPaths:
ap.skill_mgr = _make_skill_manager({'demo': _make_skill_data(name='demo', package_root=tmpdir)})
loader = NativeToolLoader(ap)
query = _make_query(query_id='q1', variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']})
query = _make_bound_query(query_id='q1', variables={PIPELINE_BOUND_SKILLS_KEY: ['demo']})
with pytest.raises(ValueError, match='Skill "demo" is not available at this path'):
await loader.invoke_tool(
@@ -717,3 +724,16 @@ class TestNativeToolLoaderSkillPaths:
{'path': '/workspace/.skills/demo/notes.txt', 'content': 'hi'},
query,
)
@pytest.mark.asyncio
async def test_native_skill_tools_require_runner_box_binding():
from langbot.pkg.provider.tools.loaders.native import NativeToolLoader
from langbot_plugin.box.errors import BoxValidationError
ap = _make_ap()
ap.box_service = SimpleNamespace(available=True, execute_tool=AsyncMock())
loader = NativeToolLoader(ap)
with pytest.raises(BoxValidationError, match='Runner must bind a Box'):
await loader.invoke_tool('exec', {'command': 'true'}, _make_query())
ap.box_service.execute_tool.assert_not_awaited()