test: tighten phase 1 coverage contracts

This commit is contained in:
huanghuoguoguo
2026-05-16 10:30:17 +08:00
parent 3ba727f0e4
commit bb55cd7ba9
44 changed files with 708 additions and 1164 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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'
})

View File

@@ -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