mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-22 16:35:20 +08:00
Merge pull request #2635 from langbot-app/test/core-contract-coverage-gaps
test(core): verify runtime behavior and close PostgreSQL coverage gaps
This commit is contained in:
@@ -867,7 +867,8 @@ class TestBotServiceSendMessage:
|
||||
with pytest.raises(Exception, match='Invalid message_chain format'):
|
||||
await service.send_message(WORKSPACE_UUID, 'bot-uuid', 'group', '123', {'invalid': 'format'})
|
||||
|
||||
async def test_send_message_valid_call(self):
|
||||
@pytest.mark.parametrize(('target_type', 'target_id'), [('group', 123), ('person', 'user-456')])
|
||||
async def test_send_message_valid_call(self, target_type, target_id):
|
||||
"""Sends message through adapter when all valid."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
@@ -881,14 +882,18 @@ class TestBotServiceSendMessage:
|
||||
service = BotService(ap)
|
||||
service.get_bot = AsyncMock(return_value={'uuid': 'bot-uuid'})
|
||||
|
||||
# Execute with valid message chain format
|
||||
message_chain_data = {'messages': [{'type': 'text', 'data': {'text': 'Hello'}}]}
|
||||
from langbot_plugin.api.entities.builtin.platform.message import MessageChain, Plain
|
||||
|
||||
# Patch the import location - the module imports inside the function
|
||||
with patch('langbot_plugin.api.entities.builtin.platform.message.MessageChain') as MockMessageChain:
|
||||
mock_chain = Mock()
|
||||
MockMessageChain.model_validate = Mock(return_value=mock_chain)
|
||||
await service.send_message(WORKSPACE_UUID, 'bot-uuid', 'group', '123', message_chain_data)
|
||||
expected_chain = MessageChain([Plain(text='Hello')])
|
||||
message_chain_data = expected_chain.model_dump()
|
||||
await service.send_message(WORKSPACE_UUID, 'bot-uuid', target_type, target_id, message_chain_data)
|
||||
|
||||
# Verify adapter.send_message was called
|
||||
runtime_bot.adapter.send_message.assert_called_once_with('group', '123', mock_chain)
|
||||
service.get_bot.assert_awaited_once_with(WORKSPACE_UUID, 'bot-uuid', include_secret=False)
|
||||
ap.platform_mgr.get_bot_by_uuid.assert_awaited_once_with(WORKSPACE_UUID, 'bot-uuid')
|
||||
runtime_bot.adapter.send_message.assert_awaited_once()
|
||||
sent_target_type, sent_target_id, sent_chain = runtime_bot.adapter.send_message.await_args.args
|
||||
assert sent_target_type == target_type
|
||||
assert sent_target_id == str(target_id)
|
||||
assert isinstance(sent_chain, MessageChain)
|
||||
assert sent_chain.model_dump() == expected_chain.model_dump()
|
||||
assert str(sent_chain) == 'Hello'
|
||||
|
||||
@@ -16,6 +16,10 @@ from __future__ import annotations
|
||||
import pytest
|
||||
import jwt
|
||||
import datetime
|
||||
|
||||
import sqlalchemy
|
||||
from argon2 import PasswordHasher
|
||||
from argon2.exceptions import VerifyMismatchError
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
from types import SimpleNamespace
|
||||
|
||||
@@ -510,10 +514,17 @@ class TestUserServiceResetPassword:
|
||||
service = UserService(ap)
|
||||
|
||||
# Execute
|
||||
await service.reset_password('test@example.com', 'new_password')
|
||||
await service.reset_password(' Test@Example.COM ', 'new_password')
|
||||
|
||||
# Verify - execute_async was called with update
|
||||
ap.persistence_mgr.execute_async.assert_called_once()
|
||||
ap.persistence_mgr.execute_async.assert_awaited_once()
|
||||
statement = ap.persistence_mgr.execute_async.await_args.args[0]
|
||||
assert isinstance(statement, sqlalchemy.sql.dml.Update)
|
||||
assert statement.table.name == User.__tablename__
|
||||
params = statement.compile().params
|
||||
assert params['normalized_email_1'] == '[email protected]'
|
||||
assert PasswordHasher().verify(params['password'], 'new_password')
|
||||
with pytest.raises(VerifyMismatchError):
|
||||
PasswordHasher().verify(params['password'], 'old-password')
|
||||
|
||||
|
||||
class TestUserServiceChangePassword:
|
||||
|
||||
@@ -377,104 +377,59 @@ class TestWebhookServiceGetWebhook:
|
||||
class TestWebhookServiceUpdateWebhook:
|
||||
"""Tests for update_webhook method."""
|
||||
|
||||
async def test_update_webhook_name_only(self):
|
||||
"""Updates only the name field."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
ap.persistence_mgr.execute_async = AsyncMock(return_value=_create_write_result())
|
||||
@pytest.mark.parametrize(
|
||||
'changes',
|
||||
[
|
||||
{'name': 'Updated Name'},
|
||||
{'url': 'https://updated.example.com'},
|
||||
{'description': 'Updated description'},
|
||||
{'enabled': False},
|
||||
{
|
||||
'name': 'All Updated',
|
||||
'url': 'https://all.updated.example.com',
|
||||
'description': 'All updated description',
|
||||
'enabled': False,
|
||||
},
|
||||
],
|
||||
ids=['name-only', 'url-only', 'description-only', 'enabled-only', 'all-fields'],
|
||||
)
|
||||
async def test_update_webhook_persists_requested_fields(self, tenant_webhook_service, changes):
|
||||
"""Persist requested fields without changing omitted fields or another Workspace."""
|
||||
original = {
|
||||
'name': 'Original Name',
|
||||
'url': 'https://original.example.com',
|
||||
'description': 'Original description',
|
||||
'enabled': True,
|
||||
}
|
||||
created = await tenant_webhook_service.create_webhook(ISOLATION_WORKSPACE_A, **original)
|
||||
other = await tenant_webhook_service.create_webhook(ISOLATION_WORKSPACE_B, **original)
|
||||
|
||||
service = WebhookService(ap)
|
||||
assert await tenant_webhook_service.update_webhook(ISOLATION_WORKSPACE_A, created['id'], **changes) is True
|
||||
|
||||
# Execute
|
||||
await service.update_webhook(WORKSPACE_UUID, 1, name='Updated Name')
|
||||
updated = await tenant_webhook_service.get_webhook(ISOLATION_WORKSPACE_A, created['id'], include_secret=True)
|
||||
assert {field: updated[field] for field in original} == original | changes
|
||||
assert updated['id'] == created['id']
|
||||
assert updated['workspace_uuid'] == ISOLATION_WORKSPACE_A
|
||||
assert (
|
||||
await tenant_webhook_service.get_webhook(ISOLATION_WORKSPACE_B, other['id'], include_secret=True) == other
|
||||
)
|
||||
|
||||
# Verify
|
||||
ap.persistence_mgr.execute_async.assert_called_once()
|
||||
|
||||
async def test_update_webhook_url_only(self):
|
||||
"""Updates only the url field."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
ap.persistence_mgr.execute_async = AsyncMock(return_value=_create_write_result())
|
||||
|
||||
service = WebhookService(ap)
|
||||
|
||||
# Execute
|
||||
await service.update_webhook(WORKSPACE_UUID, 1, url='http://updated.example.com')
|
||||
|
||||
# Verify
|
||||
ap.persistence_mgr.execute_async.assert_called_once()
|
||||
|
||||
async def test_update_webhook_description_only(self):
|
||||
"""Updates only the description field."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
ap.persistence_mgr.execute_async = AsyncMock(return_value=_create_write_result())
|
||||
|
||||
service = WebhookService(ap)
|
||||
|
||||
# Execute
|
||||
await service.update_webhook(WORKSPACE_UUID, 1, description='Updated description')
|
||||
|
||||
# Verify
|
||||
ap.persistence_mgr.execute_async.assert_called_once()
|
||||
|
||||
async def test_update_webhook_enabled_only(self):
|
||||
"""Updates only the enabled field."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
ap.persistence_mgr.execute_async = AsyncMock(return_value=_create_write_result())
|
||||
|
||||
service = WebhookService(ap)
|
||||
|
||||
# Execute
|
||||
await service.update_webhook(WORKSPACE_UUID, 1, enabled=False)
|
||||
|
||||
# Verify
|
||||
ap.persistence_mgr.execute_async.assert_called_once()
|
||||
|
||||
async def test_update_webhook_all_fields(self):
|
||||
"""Updates all fields at once."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
ap.persistence_mgr.execute_async = AsyncMock(return_value=_create_write_result())
|
||||
|
||||
service = WebhookService(ap)
|
||||
|
||||
# Execute
|
||||
await service.update_webhook(
|
||||
WORKSPACE_UUID,
|
||||
1,
|
||||
name='All Updated',
|
||||
url='http://all.updated.com',
|
||||
description='All updated description',
|
||||
async def test_update_webhook_no_fields(self, tenant_webhook_service):
|
||||
"""An empty update reports scoped existence and leaves persisted values unchanged."""
|
||||
created = await tenant_webhook_service.create_webhook(
|
||||
ISOLATION_WORKSPACE_A,
|
||||
'Unchanged',
|
||||
'https://unchanged.example.com',
|
||||
description='Keep this description',
|
||||
enabled=False,
|
||||
)
|
||||
|
||||
# Verify
|
||||
ap.persistence_mgr.execute_async.assert_called_once()
|
||||
|
||||
async def test_update_webhook_no_fields(self):
|
||||
"""Does nothing when no fields provided."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
existing = _create_mock_webhook(webhook_id=1)
|
||||
ap.persistence_mgr.execute_async = AsyncMock(return_value=_create_mock_result(first_item=existing))
|
||||
ap.persistence_mgr.serialize_model = Mock(return_value={'id': 1})
|
||||
|
||||
service = WebhookService(ap)
|
||||
|
||||
# Execute - no update parameters
|
||||
await service.update_webhook(WORKSPACE_UUID, 1)
|
||||
|
||||
# No write is issued; one scoped existence lookup is performed.
|
||||
ap.persistence_mgr.execute_async.assert_called_once()
|
||||
assert await tenant_webhook_service.update_webhook(ISOLATION_WORKSPACE_A, created['id']) is True
|
||||
assert (
|
||||
await tenant_webhook_service.get_webhook(ISOLATION_WORKSPACE_A, created['id'], include_secret=True)
|
||||
== created
|
||||
)
|
||||
assert await tenant_webhook_service.update_webhook(ISOLATION_WORKSPACE_B, created['id']) is False
|
||||
|
||||
|
||||
class TestWebhookServiceDeleteWebhook:
|
||||
@@ -695,6 +650,10 @@ async def test_update_and_delete_are_scoped(tenant_webhook_service):
|
||||
name='new',
|
||||
enabled=False,
|
||||
)
|
||||
updated = await tenant_webhook_service.get_webhook(ISOLATION_WORKSPACE_A, created['id'], include_secret=True)
|
||||
assert updated['name'] == 'new'
|
||||
assert updated['enabled'] is False
|
||||
assert updated['url'] == 'https://a.invalid/old'
|
||||
assert await tenant_webhook_service.get_enabled_webhooks(ISOLATION_WORKSPACE_A) == []
|
||||
assert await tenant_webhook_service.delete_webhook(ISOLATION_WORKSPACE_A, created['id'])
|
||||
assert await tenant_webhook_service.get_webhook(ISOLATION_WORKSPACE_A, created['id']) is None
|
||||
|
||||
Reference in New Issue
Block a user