diff --git a/src/langbot/pkg/platform/adapters/lark/event_converter.py b/src/langbot/pkg/platform/adapters/lark/event_converter.py index b2736009d..cdea19549 100644 --- a/src/langbot/pkg/platform/adapters/lark/event_converter.py +++ b/src/langbot/pkg/platform/adapters/lark/event_converter.py @@ -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]] = {} diff --git a/tests/integration/persistence/test_migrations_postgres.py b/tests/integration/persistence/test_migrations_postgres.py index babe2c207..1c0eb95c7 100644 --- a/tests/integration/persistence/test_migrations_postgres.py +++ b/tests/integration/persistence/test_migrations_postgres.py @@ -929,7 +929,7 @@ class TestPostgreSQLTenantRuntime: description='', adapter='capacity-probe', adapter_config={}, - enable=False, + enable=True, event_bindings=[], ), persistence_pipeline.LegacyPipeline( diff --git a/tests/unit_tests/agent/test_debug_prompt.py b/tests/unit_tests/agent/test_debug_prompt.py index 1ba35abf1..8ccbd4004 100644 --- a/tests/unit_tests/agent/test_debug_prompt.py +++ b/tests/unit_tests/agent/test_debug_prompt.py @@ -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] diff --git a/tests/unit_tests/api/http/service/test_tenant_resource_isolation.py b/tests/unit_tests/api/http/service/test_tenant_resource_isolation.py index 623822379..d0c58c36a 100644 --- a/tests/unit_tests/api/http/service/test_tenant_resource_isolation.py +++ b/tests/unit_tests/api/http/service/test_tenant_resource_isolation.py @@ -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', } diff --git a/tests/unit_tests/api/service/test_agent_service.py b/tests/unit_tests/api/service/test_agent_service.py index 600c3e6d3..7ff628dbf 100644 --- a/tests/unit_tests/api/service/test_agent_service.py +++ b/tests/unit_tests/api/service/test_agent_service.py @@ -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(): diff --git a/tests/unit_tests/api/service/test_bot_service.py b/tests/unit_tests/api/service/test_bot_service.py index 81a180a75..4d292f40e 100644 --- a/tests/unit_tests/api/service/test_bot_service.py +++ b/tests/unit_tests/api/service/test_bot_service.py @@ -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) diff --git a/tests/unit_tests/api/service/test_monitoring_executions.py b/tests/unit_tests/api/service/test_monitoring_executions.py index ce3d79f34..151d4b028 100644 --- a/tests/unit_tests/api/service/test_monitoring_executions.py +++ b/tests/unit_tests/api/service/test_monitoring_executions.py @@ -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'}, } diff --git a/tests/unit_tests/api/test_bots_controller_errors.py b/tests/unit_tests/api/test_bots_controller_errors.py index 12920a83b..8d2614c61 100644 --- a/tests/unit_tests/api/test_bots_controller_errors.py +++ b/tests/unit_tests/api/test_bots_controller_errors.py @@ -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}) diff --git a/tests/unit_tests/api/test_pipeline_service_defaults.py b/tests/unit_tests/api/test_pipeline_service_defaults.py index dd7687d87..7f36a185e 100644 --- a/tests/unit_tests/api/test_pipeline_service_defaults.py +++ b/tests/unit_tests/api/test_pipeline_service_defaults.py @@ -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'] == '' diff --git a/tests/unit_tests/persistence/test_tenant_uow.py b/tests/unit_tests/persistence/test_tenant_uow.py index 7b624d2ee..ee4c0b570 100644 --- a/tests/unit_tests/persistence/test_tenant_uow.py +++ b/tests/unit_tests/persistence/test_tenant_uow.py @@ -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)),), {}) diff --git a/tests/unit_tests/platform/test_botmgr_tenancy.py b/tests/unit_tests/platform/test_botmgr_tenancy.py index ac100bd46..9e6b61667 100644 --- a/tests/unit_tests/platform/test_botmgr_tenancy.py +++ b/tests/unit_tests/platform/test_botmgr_tenancy.py @@ -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( diff --git a/tests/unit_tests/platform/test_lark_eba_adapter.py b/tests/unit_tests/platform/test_lark_eba_adapter.py index 827a2f4f6..eb3a73226 100644 --- a/tests/unit_tests/platform/test_lark_eba_adapter.py +++ b/tests/unit_tests/platform/test_lark_eba_adapter.py @@ -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( diff --git a/tests/unit_tests/platform/test_omni_mainline_parity.py b/tests/unit_tests/platform/test_omni_mainline_parity.py index 7e1b8c5eb..d83cbf4e6 100644 --- a/tests/unit_tests/platform/test_omni_mainline_parity.py +++ b/tests/unit_tests/platform/test_omni_mainline_parity.py @@ -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'] diff --git a/tests/unit_tests/platform/test_routing_rules.py b/tests/unit_tests/platform/test_routing_rules.py index ce93c6099..37d63d567 100644 --- a/tests/unit_tests/platform/test_routing_rules.py +++ b/tests/unit_tests/platform/test_routing_rules.py @@ -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())