mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-10 07:46:02 +00:00
test: tighten phase 1 coverage contracts
This commit is contained in:
@@ -9,7 +9,7 @@ Source: src/langbot/pkg/api/http/service/apikey.py
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from types import SimpleNamespace
|
||||
|
||||
from langbot.pkg.api.http.service.apikey import ApiKeyService
|
||||
@@ -101,43 +101,42 @@ class TestApiKeyServiceCreateApiKey:
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
|
||||
# Mock insert result
|
||||
insert_result = Mock()
|
||||
insert_result.all = Mock(return_value=[])
|
||||
|
||||
# Mock select result for retrieving created key
|
||||
created_key = Mock(spec=ApiKey)
|
||||
created_key.id = 1
|
||||
created_key.name = 'New Key'
|
||||
created_key.key = 'lbk_generated_key'
|
||||
created_key.key = 'lbk_fixed-token'
|
||||
created_key.description = 'Test description'
|
||||
select_result = Mock()
|
||||
select_result.first = Mock(return_value=created_key)
|
||||
insert_params = []
|
||||
|
||||
# execute_async returns different results for insert vs select
|
||||
async def mock_execute(query):
|
||||
# First call is insert, second is select
|
||||
if hasattr(query, 'values'):
|
||||
return insert_result
|
||||
params = query.compile().params
|
||||
if {'name', 'key', 'description'}.issubset(params):
|
||||
insert_params.append(params)
|
||||
return Mock()
|
||||
return select_result
|
||||
|
||||
ap.persistence_mgr.execute_async = AsyncMock(side_effect=mock_execute)
|
||||
ap.persistence_mgr.serialize_model = Mock(
|
||||
return_value={
|
||||
side_effect=lambda model_cls, entity: {
|
||||
'id': 1,
|
||||
'name': 'New Key',
|
||||
'key': 'lbk_generated_key',
|
||||
'description': 'Test description',
|
||||
'name': entity.name,
|
||||
'key': entity.key,
|
||||
'description': entity.description,
|
||||
}
|
||||
)
|
||||
|
||||
service = ApiKeyService(ap)
|
||||
|
||||
# Execute
|
||||
result = await service.create_api_key('New Key', 'Test description')
|
||||
with patch('langbot.pkg.api.http.service.apikey.secrets.token_urlsafe', return_value='fixed-token'):
|
||||
result = await service.create_api_key('New Key', 'Test description')
|
||||
|
||||
# Verify key format
|
||||
assert insert_params == [
|
||||
{'name': 'New Key', 'key': 'lbk_fixed-token', 'description': 'Test description'}
|
||||
]
|
||||
assert result['key'].startswith('lbk_')
|
||||
assert result['key'] == 'lbk_fixed-token'
|
||||
assert result['name'] == 'New Key'
|
||||
assert result['description'] == 'Test description'
|
||||
|
||||
@@ -313,8 +312,8 @@ class TestApiKeyServiceVerifyApiKey:
|
||||
# Verify
|
||||
assert result is False
|
||||
|
||||
async def test_verify_api_key_wrong_prefix(self):
|
||||
"""Returns False for key without correct prefix."""
|
||||
async def test_verify_api_key_unknown_key(self):
|
||||
"""Returns False when the key is not present in persistence."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
@@ -326,7 +325,7 @@ class TestApiKeyServiceVerifyApiKey:
|
||||
service = ApiKeyService(ap)
|
||||
|
||||
# Execute
|
||||
result = await service.verify_api_key('invalid_prefix_key')
|
||||
result = await service.verify_api_key('unknown_key')
|
||||
|
||||
# Verify
|
||||
assert result is False
|
||||
@@ -427,4 +426,4 @@ class TestApiKeyServiceUpdateApiKey:
|
||||
await service.update_api_key(1)
|
||||
|
||||
# Verify - no execute call since no update_data
|
||||
ap.persistence_mgr.execute_async.assert_not_called()
|
||||
ap.persistence_mgr.execute_async.assert_not_called()
|
||||
|
||||
@@ -432,7 +432,7 @@ class TestBotServiceUpdateBot:
|
||||
"""Tests for update_bot method."""
|
||||
|
||||
async def test_update_bot_removes_uuid_from_data(self):
|
||||
"""Removes uuid field from update data."""
|
||||
"""Does not persist caller-provided uuid in update payload."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
@@ -456,9 +456,9 @@ class TestBotServiceUpdateBot:
|
||||
update_data = {'uuid': 'should-be-removed', 'name': 'Updated Name'}
|
||||
await service.update_bot('test-uuid', update_data)
|
||||
|
||||
# Verify - uuid was removed from bot_data dict
|
||||
assert 'uuid' not in update_data
|
||||
assert 'name' in update_data
|
||||
update_params = ap.persistence_mgr.execute_async.await_args_list[0].args[0].compile().params
|
||||
assert update_params['name'] == 'Updated Name'
|
||||
assert 'should-be-removed' not in update_params.values()
|
||||
|
||||
async def test_update_bot_pipeline_not_found_raises(self):
|
||||
"""Raises Exception when updating with nonexistent pipeline UUID."""
|
||||
@@ -513,7 +513,9 @@ class TestBotServiceUpdateBot:
|
||||
# Execute
|
||||
await service.update_bot('test-uuid', {'use_pipeline_uuid': 'pipeline-uuid'})
|
||||
|
||||
# Verify - pipeline name was captured
|
||||
update_params = ap.persistence_mgr.execute_async.await_args_list[1].args[0].compile().params
|
||||
assert update_params['use_pipeline_uuid'] == 'pipeline-uuid'
|
||||
assert update_params['use_pipeline_name'] == 'Updated Pipeline'
|
||||
|
||||
|
||||
class TestBotServiceDeleteBot:
|
||||
@@ -657,4 +659,4 @@ class TestBotServiceSendMessage:
|
||||
await service.send_message('bot-uuid', 'group', '123', message_chain_data)
|
||||
|
||||
# Verify adapter.send_message was called
|
||||
runtime_bot.adapter.send_message.assert_called_once_with('group', '123', mock_chain)
|
||||
runtime_bot.adapter.send_message.assert_called_once_with('group', '123', mock_chain)
|
||||
|
||||
@@ -153,7 +153,7 @@ class TestCreateKnowledgeBase:
|
||||
|
||||
service = knowledge_module.KnowledgeService(mock_app)
|
||||
|
||||
result = await service.create_knowledge_base({
|
||||
await service.create_knowledge_base({
|
||||
'knowledge_engine_plugin_id': 'author/engine'
|
||||
})
|
||||
|
||||
|
||||
@@ -318,24 +318,22 @@ class TestPipelineServiceCreatePipeline:
|
||||
service.get_pipelines = AsyncMock(return_value=[])
|
||||
service.get_pipeline = AsyncMock(return_value={
|
||||
'uuid': 'new-uuid',
|
||||
'extensions_preferences': {
|
||||
'enable_all_plugins': True,
|
||||
'enable_all_mcp_servers': True,
|
||||
'plugins': [],
|
||||
'mcp_servers': [],
|
||||
}
|
||||
'extensions_preferences': {},
|
||||
})
|
||||
|
||||
ap.persistence_mgr.execute_async = AsyncMock()
|
||||
insert_params = []
|
||||
|
||||
async def mock_execute(query):
|
||||
params = query.compile().params
|
||||
if 'extensions_preferences' in params:
|
||||
insert_params.append(params)
|
||||
return Mock()
|
||||
|
||||
ap.persistence_mgr.execute_async = AsyncMock(side_effect=mock_execute)
|
||||
ap.persistence_mgr.serialize_model = Mock(
|
||||
return_value={
|
||||
'uuid': 'new-uuid',
|
||||
'extensions_preferences': {
|
||||
'enable_all_plugins': True,
|
||||
'enable_all_mcp_servers': True,
|
||||
'plugins': [],
|
||||
'mcp_servers': [],
|
||||
}
|
||||
'extensions_preferences': {},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -344,8 +342,13 @@ class TestPipelineServiceCreatePipeline:
|
||||
with patch('langbot.pkg.utils.paths.get_resource_path', return_value='templates/default-pipeline-config.json'):
|
||||
await service.create_pipeline({'name': 'New Pipeline'})
|
||||
|
||||
# Verify - extensions_preferences should have been set
|
||||
ap.persistence_mgr.execute_async.assert_called()
|
||||
assert len(insert_params) == 1
|
||||
assert insert_params[0]['extensions_preferences'] == {
|
||||
'enable_all_plugins': True,
|
||||
'enable_all_mcp_servers': True,
|
||||
'plugins': [],
|
||||
'mcp_servers': [],
|
||||
}
|
||||
|
||||
|
||||
class _MockResultWithBots:
|
||||
@@ -364,7 +367,7 @@ class TestPipelineServiceUpdatePipeline:
|
||||
"""Tests for update_pipeline method."""
|
||||
|
||||
async def test_update_pipeline_removes_protected_fields(self):
|
||||
"""Removes uuid, for_version, stages, is_default from update data."""
|
||||
"""Does not persist protected fields from update data."""
|
||||
# Setup
|
||||
ap = SimpleNamespace()
|
||||
ap.persistence_mgr = SimpleNamespace()
|
||||
@@ -390,12 +393,11 @@ class TestPipelineServiceUpdatePipeline:
|
||||
}
|
||||
await service.update_pipeline('test-uuid', pipeline_data)
|
||||
|
||||
# Verify - protected fields removed
|
||||
assert 'uuid' not in pipeline_data
|
||||
assert 'for_version' not in pipeline_data
|
||||
assert 'stages' not in pipeline_data
|
||||
assert 'is_default' not in pipeline_data
|
||||
assert 'description' in pipeline_data
|
||||
update_params = ap.persistence_mgr.execute_async.await_args_list[0].args[0].compile().params
|
||||
assert update_params['description'] == 'New description'
|
||||
assert 'should-be-removed' not in update_params.values()
|
||||
assert ['should-be-removed'] not in update_params.values()
|
||||
assert not any(value is True for value in update_params.values())
|
||||
|
||||
async def test_update_pipeline_syncs_bot_names(self):
|
||||
"""Updates bot use_pipeline_name when pipeline name changes."""
|
||||
@@ -826,4 +828,4 @@ class TestDefaultStageOrder:
|
||||
def test_default_stage_order_contains_key_stages(self):
|
||||
"""Default stage order contains key processing stages."""
|
||||
assert 'MessageProcessor' in default_stage_order
|
||||
assert 'SendResponseBackStage' in default_stage_order
|
||||
assert 'SendResponseBackStage' in default_stage_order
|
||||
|
||||
Reference in New Issue
Block a user