test(acceptance): align fixtures with accepted runtime contracts

This commit is contained in:
RockChinQ
2026-10-09 16:48:15 +00:00
parent 6175e30210
commit 9c4d383bb0
14 changed files with 65 additions and 55 deletions
@@ -46,7 +46,9 @@ class LarkEventConverter(abstract_platform_adapter.AbstractEventConverter):
operator = cls._event_user(data.get('operator_id'))
if event_type == 'im.chat.member.bot.deleted_v1':
return [platform_events.BotRemovedFromGroupEvent(group=group, operator=operator, **common)]
if event_type in {'im.chat.member.user.added_v1', 'im.chat.member.user.deleted_v1'}:
if event_type in {
'im.chat.member.user.added_v1', 'im.chat.member.user.deleted_v1'
}:
events = []
for user in data.get('users') or []:
member = cls._event_user(user.get('user_id'), user.get('name') or '')
@@ -54,25 +56,15 @@ class LarkEventConverter(abstract_platform_adapter.AbstractEventConverter):
continue
if event_type == 'im.chat.member.user.added_v1':
inviter = operator if operator and operator.id != member.id else None
events.append(
platform_events.MemberJoinedEvent(
group=group,
member=member,
inviter=inviter,
join_type='invite' if inviter else None,
**common,
)
)
events.append(platform_events.MemberJoinedEvent(
group=group, member=member, inviter=inviter,
join_type='invite' if inviter else None, **common,
))
else:
events.append(
platform_events.MemberLeftEvent(
group=group,
member=member,
operator=operator,
is_kicked=operator is not None and operator.id != member.id,
**common,
)
)
events.append(platform_events.MemberLeftEvent(
group=group, member=member, operator=operator,
is_kicked=operator is not None and operator.id != member.id, **common,
))
return events
if event_type == 'im.chat.updated_v1':
after = data.get('after_change') or {}
@@ -85,14 +77,9 @@ class LarkEventConverter(abstract_platform_adapter.AbstractEventConverter):
group.avatar_url = after.get('avatar')
owner = cls._event_user(after.get('owner_id'))
group.owner_id = owner.id if owner else None
return [
platform_events.GroupInfoUpdatedEvent(
group=group,
operator=operator,
changed_fields=sorted(changed),
**common,
)
]
return [platform_events.GroupInfoUpdatedEvent(
group=group, operator=operator, changed_fields=sorted(changed), **common,
)]
context = message_context or {}
if event_type == 'im.message.recalled_v1':
return [platform_events.MessageDeletedEvent(message_id=data['message_id'], **context, **common)]
@@ -100,16 +87,11 @@ class LarkEventConverter(abstract_platform_adapter.AbstractEventConverter):
user = cls._event_user(data.get('user_id'))
if user is None:
user = platform_entities.User(id=data.get('app_id') or '', is_bot=data.get('operator_type') == 'app')
return [
platform_events.MessageReactionEvent(
message_id=data['message_id'],
user=user,
reaction=(data.get('reaction_type') or {}).get('emoji_type') or '',
is_add=event_type == 'im.message.reaction.created_v1',
**context,
**common,
)
]
return [platform_events.MessageReactionEvent(
message_id=data['message_id'], user=user,
reaction=(data.get('reaction_type') or {}).get('emoji_type') or '',
is_add=event_type == 'im.message.reaction.created_v1', **context, **common,
)]
return []
_processed_thread_quote_cache: typing.ClassVar[dict[str, float]] = {}
@@ -929,7 +929,7 @@ class TestPostgreSQLTenantRuntime:
description='',
adapter='capacity-probe',
adapter_config={},
enable=False,
enable=True,
event_bindings=[],
),
persistence_pipeline.LegacyPipeline(
+5 -1
View File
@@ -152,6 +152,9 @@ async def harness():
)
ap.plugin_connector = PromptPullConnector(ap)
ap.agent_run_orchestrator = AgentRunOrchestrator(ap, ap.runner_registry)
from langbot.pkg.api.http.service.model import LLMModelsService
ap.llm_model_service = LLMModelsService(ap)
ap.agent_service = AgentService(ap)
try:
yield ap
@@ -315,4 +318,5 @@ async def test_non_debug_query_keeps_effective_prompt_instead_of_static_config(h
assert query.prompt is original
assert pulled == [message.model_dump(mode='json') for message in original.messages]
else:
assert pulled == []
# Event-only runs have no pipeline-prepared query; use the runner prompt.
assert pulled == [Message(**message).model_dump(mode='json') for message in DEFAULT_PROMPT]
@@ -294,7 +294,7 @@ async def test_cross_workspace_parent_references_are_rejected(tenant_services):
{
'event_bindings': [
{
'event_pattern': 'message.*',
'event_pattern': 'message.received',
'target_type': 'pipeline',
'target_uuid': 'pipeline-b',
}
@@ -99,6 +99,9 @@ def _make_app():
get=AsyncMock(return_value=SimpleNamespace(usages=['agent'], config_schema=[])),
list_runners=AsyncMock(return_value=[]),
)
from langbot.pkg.api.http.service.model import LLMModelsService
app.llm_model_service = LLMModelsService(app)
app.tool_mgr = None
app.logger = Mock()
return app
@@ -877,7 +880,9 @@ async def test_event_processor_rejects_invalid_component_and_missing_parameters(
)
)
with pytest.raises(ValueError, match='Required processor parameter'):
await service.create_agent(WORKSPACE_UUID, {'kind': 'event_processor', 'component_ref': 'plugin:a/b/c'})
await service.create_agent(
WORKSPACE_UUID, {'kind': 'event_processor', 'component_ref': 'plugin:a/b/c', 'parameters': {}}
)
async def test_unavailable_event_processor_can_still_be_renamed():
@@ -390,14 +390,14 @@ class TestBotServiceCreateBot:
async def mock_execute(query):
nonlocal call_count
call_count += 1
if call_count <= 2:
return pipeline_result # First call: check pipeline
elif call_count == 3:
return Mock() # Insert
if call_count == 1:
return Mock() # Insert; bot creation no longer queries legacy pipelines
return bot_result # Get bot
ap.persistence_mgr.execute_async = AsyncMock(side_effect=mock_execute)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'new-uuid', 'name': 'New Bot'})
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'new-uuid', 'name': 'New Bot', 'adapter': 'telegram', 'enable': True}
)
service = BotService(ap)
@@ -74,9 +74,9 @@ async def test_event_properties_survive_detail_snapshot_fallback(engine, service
await session.commit()
detail = await service.get_execution_detail(WORKSPACE, 'agent', 'custom-run')
items = detail['pages']['inputs']['items']
assert items[0]['content']['member'] == data['member']
assert items[0]['content']['count'] == 0
assert items[0]['content']['custom'] == data['custom']
assert items[0]['content']['event']['member'] == data['member']
assert items[0]['content']['event']['count'] == 0
assert items[0]['content']['event']['custom'] == data['custom']
if has_log:
assert items[1]['content'] == {'text': 'later'}
@@ -85,6 +85,7 @@ def test_event_content_preserves_conflicting_custom_input_fields():
from langbot.pkg.api.http.service.monitoring_execution_details import event_content
assert event_content({'text': 'normalized'}, {'text': {'custom': False}}) == {
'text': 'event',
'event': {'text': {'custom': False}},
'input': {'text': 'normalized'},
}
@@ -105,6 +105,7 @@ async def test_runtime_error_redacts_persisted_secrets_on_partial_update():
from langbot.pkg.api.http.service.bot_errors import BotApplyError
ap = SimpleNamespace(
sess_mgr=SimpleNamespace(session_list=[]),
persistence_mgr=SimpleNamespace(execute_async=AsyncMock(return_value=SimpleNamespace(rowcount=1))),
platform_mgr=SimpleNamespace(
remove_bot=AsyncMock(),
@@ -113,7 +114,7 @@ async def test_runtime_error_redacts_persisted_secrets_on_partial_update():
)
service = BotService(ap)
service.get_bot = AsyncMock(
return_value={'uuid': 'bot-1', 'adapter_config': {'app_secret': 'persisted-secret-value'}}
return_value={'uuid': 'bot-1', 'enable': True, 'adapter_config': {'app_secret': 'persisted-secret-value'}}
)
with pytest.raises(BotApplyError) as captured:
await service.update_bot('workspace-test', 'bot-1', {'enable': True})
@@ -55,6 +55,9 @@ async def test_default_pipeline_config_uses_first_installed_runner_schema():
runner_registry=FakeRegistry([custom_agent, local_agent]),
)
from langbot.pkg.api.http.service.model import LLMModelsService
ap.llm_model_service = LLMModelsService(ap)
config = await PipelineService(ap).get_default_pipeline_config('workspace-test')
assert config['ai']['runner']['id'] == 'plugin:alice/custom-agent/default'
@@ -72,6 +75,9 @@ async def test_default_pipeline_config_stays_neutral_without_installed_runners()
runner_registry=FakeRegistry([]),
)
from langbot.pkg.api.http.service.model import LLMModelsService
ap.llm_model_service = LLMModelsService(ap)
config = await PipelineService(ap).get_default_pipeline_config('workspace-test')
assert config['ai']['runner']['id'] == ''
@@ -984,10 +984,10 @@ async def test_monitoring_json_casts_are_limited_to_journal_metadata() -> None:
from langbot.pkg.entity.persistence.agent_run import AgentRun
from langbot.pkg.entity.persistence.event_log import EventLog
for column in (AgentRun.metadata_json, EventLog.metadata_json):
for column in (AgentRun.metadata_json, EventLog.metadata_json, EventLog.input_json):
statement = sa.select(sa.cast(column, sa.JSON)['status'].as_string())
_validate_scoped_statement_call((statement,), {})
for column in (AgentRun.authorization_json, EventLog.input_json, sa.literal('{}')):
for column in (AgentRun.authorization_json, sa.literal('{}')):
with pytest.raises(ScopedSessionTransactionError, match='casts'):
_validate_scoped_statement_call((sa.select(sa.cast(column, sa.JSON)),), {})
@@ -277,7 +277,7 @@ async def test_cloud_startup_reuses_validated_platform_binding(saved_adapter, re
description='',
adapter=saved_adapter,
adapter_config={},
enable=False,
enable=True,
event_bindings=[],
)
workspace_service = SimpleNamespace(
@@ -68,6 +68,9 @@ def message_item(message_id='msg-remote'):
class DummyAPIClient:
def __init__(self):
identity_response = DummyResponse()
identity_response.raw.content = json.dumps({'bot': {'open_id': 'bot-open-id'}})
self.arequest = AsyncMock(return_value=identity_response)
self.im = SimpleNamespace(
v1=SimpleNamespace(
message=SimpleNamespace(
@@ -39,7 +39,13 @@ def chain(text):
def test_omni_config_contains_mainline_options_and_help(name):
legacy = yaml.safe_load((PLATFORM / 'sources' / f'{name}.yaml').read_text())['spec']
omni = yaml.safe_load((PLATFORM / 'adapters' / name / 'manifest.yaml').read_text())['spec']
assert {c['name'] for c in legacy['config']} <= {c['name'] for c in omni['config']}
legacy_options = {c['name'] for c in legacy['config']}
omni_options = {c['name'] for c in omni['config']}
if name == 'lark':
# Identity is discovered through the bot info API, not a configured name.
legacy_options.remove('bot_name')
assert 'bot_name' not in omni_options
assert legacy_options <= omni_options
assert omni['help_links'] == legacy['help_links']
@@ -170,7 +170,9 @@ class TestEventRouteTrace:
agent_service=SimpleNamespace(get_agent=AsyncMock(side_effect=[malformed_agent, valid_agent])),
agent_run_orchestrator=SimpleNamespace(run=fake_run),
)
event = SimpleNamespace(type='platform.member.joined')
from langbot_plugin.api.entities.builtin.platform.events import EBAEvent
event = EBAEvent(type='platform.member.joined')
failed = await bot._dispatch_eba_event_to_processor(event, Mock())
delivered = await bot._dispatch_eba_event_to_processor(event, Mock())