test: format test suite

This commit is contained in:
huanghuoguoguo
2026-06-16 11:13:05 +08:00
parent 1ae5aacc00
commit ff0c5a6f0a
92 changed files with 1658 additions and 1713 deletions
+1 -1
View File
@@ -1 +1 @@
"""Unit tests for LangBot API HTTP service layer."""
"""Unit tests for LangBot API HTTP service layer."""
+1 -1
View File
@@ -13,4 +13,4 @@ Does NOT:
- Call real provider/platform/network
Uses tests.factories.FakeApp as base mock application.
"""
"""
@@ -132,9 +132,7 @@ class TestApiKeyServiceCreateApiKey:
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')
assert insert_params == [
{'name': 'New Key', 'key': 'lbk_fixed-token', 'description': 'Test description'}
]
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'
@@ -303,13 +303,7 @@ class TestBotServiceCreateBot:
ap = SimpleNamespace()
ap.persistence_mgr = SimpleNamespace()
ap.instance_config = SimpleNamespace()
ap.instance_config.data = {
'system': {
'limitation': {
'max_bots': 2
}
}
}
ap.instance_config.data = {'system': {'limitation': {'max_bots': 2}}}
ap.platform_mgr = SimpleNamespace()
ap.platform_mgr.load_bot = AsyncMock()
@@ -318,9 +312,7 @@ class TestBotServiceCreateBot:
bot2 = _create_mock_bot(bot_uuid='uuid-2')
mock_result = _create_mock_result([bot1, bot2])
ap.persistence_mgr.execute_async = AsyncMock(return_value=mock_result)
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'uuid-1', 'name': 'Bot 1'}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'uuid-1', 'name': 'Bot 1'})
service = BotService(ap)
@@ -352,6 +344,7 @@ class TestBotServiceCreateBot:
bot_result.first = Mock(return_value=_create_mock_bot())
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -362,9 +355,7 @@ class TestBotServiceCreateBot:
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'})
service = BotService(ap)
@@ -397,6 +388,7 @@ class TestBotServiceCreateBot:
bot_result.first = Mock(return_value=_create_mock_bot())
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -492,6 +484,7 @@ class TestBotServiceUpdateBot:
pipeline_result.first = Mock(return_value=mock_pipeline)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -582,10 +575,9 @@ class TestBotServiceListEventLogs:
# Mock runtime bot with logger
runtime_bot = SimpleNamespace()
runtime_bot.logger = SimpleNamespace()
runtime_bot.logger.get_logs = AsyncMock(return_value=(
[SimpleNamespace(to_json=Mock(return_value={'msg': 'log1'}))],
5
))
runtime_bot.logger.get_logs = AsyncMock(
return_value=([SimpleNamespace(to_json=Mock(return_value={'msg': 'log1'}))], 5)
)
ap.platform_mgr.get_bot_by_uuid = AsyncMock(return_value=runtime_bot)
service = BotService(ap)
@@ -646,11 +638,7 @@ class TestBotServiceSendMessage:
service = BotService(ap)
# Execute with valid message chain format
message_chain_data = {
'messages': [
{'type': 'text', 'data': {'text': 'Hello'}}
]
}
message_chain_data = {'messages': [{'type': 'text', 'data': {'text': 'Hello'}}]}
# Patch the import location - the module imports inside the function
with patch('langbot_plugin.api.entities.builtin.platform.message.MessageChain') as MockMessageChain:
@@ -6,6 +6,7 @@ Tests cover:
- Knowledge engine discovery
- File operations
"""
from __future__ import annotations
import pytest
@@ -52,9 +53,7 @@ class TestGetKnowledgeBases:
"""Test that it returns all knowledge base details."""
knowledge_module = get_knowledge_service_module()
mock_app = create_mock_app()
mock_app.rag_mgr.get_all_knowledge_base_details = AsyncMock(
return_value=[{'uuid': 'kb1', 'name': 'KB1'}]
)
mock_app.rag_mgr.get_all_knowledge_base_details = AsyncMock(return_value=[{'uuid': 'kb1', 'name': 'KB1'}])
service = knowledge_module.KnowledgeService(mock_app)
result = await service.get_knowledge_bases()
@@ -83,9 +82,7 @@ class TestGetKnowledgeBase:
"""Test that it returns specific KB details."""
knowledge_module = get_knowledge_service_module()
mock_app = create_mock_app()
mock_app.rag_mgr.get_knowledge_base_details = AsyncMock(
return_value={'uuid': 'kb1', 'name': 'KB1'}
)
mock_app.rag_mgr.get_knowledge_base_details = AsyncMock(return_value={'uuid': 'kb1', 'name': 'KB1'})
service = knowledge_module.KnowledgeService(mock_app)
result = await service.get_knowledge_base('kb1')
@@ -153,9 +150,7 @@ class TestCreateKnowledgeBase:
service = knowledge_module.KnowledgeService(mock_app)
await service.create_knowledge_base({
'knowledge_engine_plugin_id': 'author/engine'
})
await service.create_knowledge_base({'knowledge_engine_plugin_id': 'author/engine'})
# Check that default name 'Untitled' was used
call_args = mock_app.rag_mgr.create_knowledge_base.call_args
@@ -170,20 +165,21 @@ class TestUpdateKnowledgeBase:
"""Test that only mutable fields are updated."""
knowledge_module = get_knowledge_service_module()
mock_app = create_mock_app()
mock_app.rag_mgr.get_knowledge_base_details = AsyncMock(
return_value={'uuid': 'kb1', 'name': 'Updated'}
)
mock_app.rag_mgr.get_knowledge_base_details = AsyncMock(return_value={'uuid': 'kb1', 'name': 'Updated'})
mock_app.rag_mgr.remove_knowledge_base_from_runtime = AsyncMock()
mock_app.rag_mgr.load_knowledge_base = AsyncMock()
service = knowledge_module.KnowledgeService(mock_app)
# Pass both mutable and immutable fields
await service.update_knowledge_base('kb1', {
'name': 'New Name',
'description': 'New desc',
'uuid': 'should_be_filtered', # immutable
})
await service.update_knowledge_base(
'kb1',
{
'name': 'New Name',
'description': 'New desc',
'uuid': 'should_be_filtered', # immutable
},
)
# Check that only mutable fields were passed to update
call_args = mock_app.persistence_mgr.execute_async.call_args
@@ -288,9 +284,7 @@ class TestListKnowledgeEngines:
"""Test that it returns empty list and logs warning on exception."""
knowledge_module = get_knowledge_service_module()
mock_app = create_mock_app()
mock_app.plugin_connector.list_knowledge_engines = AsyncMock(
side_effect=Exception('Connection error')
)
mock_app.plugin_connector.list_knowledge_engines = AsyncMock(side_effect=Exception('Connection error'))
service = knowledge_module.KnowledgeService(mock_app)
result = await service.list_knowledge_engines()
@@ -386,12 +380,10 @@ class TestGetEngineSchemas:
"""Test that it returns empty dict and logs warning on exception."""
knowledge_module = get_knowledge_service_module()
mock_app = create_mock_app()
mock_app.plugin_connector.get_rag_creation_schema = AsyncMock(
side_effect=Exception('Plugin error')
)
mock_app.plugin_connector.get_rag_creation_schema = AsyncMock(side_effect=Exception('Plugin error'))
service = knowledge_module.KnowledgeService(mock_app)
result = await service.get_engine_creation_schema('author/engine')
assert result == {}
mock_app.logger.warning.assert_called_once()
mock_app.logger.warning.assert_called_once()
@@ -174,9 +174,7 @@ class TestMaintenanceServiceGetStorageAnalysis:
# Setup
ap = SimpleNamespace()
ap.instance_config = SimpleNamespace()
ap.instance_config.data = {
'database': {'use': 'sqlite', 'sqlite': {'path': 'data/langbot.db'}}
}
ap.instance_config.data = {'database': {'use': 'sqlite', 'sqlite': {'path': 'data/langbot.db'}}}
ap.persistence_mgr = SimpleNamespace()
ap.logger = SimpleNamespace()
ap.logger.warning = Mock()
@@ -292,12 +290,8 @@ class TestMaintenanceServiceGetStorageAnalysis:
service._file_count = Mock(return_value=0)
service._monitoring_counts = AsyncMock(return_value={})
service._binary_storage_stats = AsyncMock(return_value={'count': 0, 'size_bytes': 0})
service._expired_uploaded_candidates = AsyncMock(return_value=[
{'key': 'old_file', 'size_bytes': 100}
])
service._expired_log_candidates = Mock(return_value=[
{'name': 'old_log', 'size_bytes': 50}
])
service._expired_uploaded_candidates = AsyncMock(return_value=[{'key': 'old_file', 'size_bytes': 100}])
service._expired_log_candidates = Mock(return_value=[{'name': 'old_log', 'size_bytes': 50}])
# Execute
result = await service.get_storage_analysis()
@@ -367,6 +361,7 @@ class TestMaintenanceServiceBinaryStorageStats:
size_result = _create_mock_result(scalar_value=5000)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -396,6 +391,7 @@ class TestMaintenanceServiceBinaryStorageStats:
count_result = _create_mock_result(scalar_value=5)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -821,4 +817,4 @@ class TestMaintenanceServiceExpiredLocalUploadCandidates:
result = service._expired_local_upload_candidates(7, include_paths=True)
# Verify - path included
assert 'path' in result[0]
assert 'path' in result[0]
@@ -186,13 +186,7 @@ class TestMCPServiceCreateMCPServer:
ap = SimpleNamespace()
ap.persistence_mgr = SimpleNamespace()
ap.instance_config = SimpleNamespace()
ap.instance_config.data = {
'system': {
'limitation': {
'max_extensions': 2
}
}
}
ap.instance_config.data = {'system': {'limitation': {'max_extensions': 2}}}
ap.plugin_connector = SimpleNamespace()
ap.plugin_connector.list_plugins = AsyncMock(return_value=[Mock(), Mock()]) # 2 plugins
@@ -252,6 +246,7 @@ class TestMCPServiceCreateMCPServer:
server_entity = _create_mock_mcp_server(server_uuid='new-uuid', enable=True)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -361,6 +356,7 @@ class TestMCPServiceUpdateMCPServer:
old_server = _create_mock_mcp_server(name='Old Server', enable=True)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -394,6 +390,7 @@ class TestMCPServiceUpdateMCPServer:
updated_server = _create_mock_mcp_server(name='Old Server', enable=True)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -432,6 +429,7 @@ class TestMCPServiceUpdateMCPServer:
# Mock for: first select -> update -> second select (for updated server)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -465,6 +463,7 @@ class TestMCPServiceUpdateMCPServer:
# Mock execute for select and update
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -499,6 +498,7 @@ class TestMCPServiceDeleteMCPServer:
server = _create_mock_mcp_server(name='Server to Delete')
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -530,6 +530,7 @@ class TestMCPServiceDeleteMCPServer:
server = _create_mock_mcp_server(name='Not in Sessions')
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -559,6 +560,7 @@ class TestMCPServiceDeleteMCPServer:
# No server found
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -596,9 +598,7 @@ class TestMCPServiceTestMCPServer:
ap.tool_mgr.mcp_tool_loader.get_session = Mock(return_value=mock_session)
ap.task_mgr = SimpleNamespace()
ap.task_mgr.create_user_task = Mock(
return_value=SimpleNamespace(id=123)
)
ap.task_mgr.create_user_task = Mock(return_value=SimpleNamespace(id=123))
service = MCPService(ap)
@@ -634,9 +634,7 @@ class TestMCPServiceTestMCPServer:
ap.tool_mgr.mcp_tool_loader.load_mcp_server = AsyncMock(return_value=mock_session)
ap.task_mgr = SimpleNamespace()
ap.task_mgr.create_user_task = Mock(
return_value=SimpleNamespace(id=456)
)
ap.task_mgr.create_user_task = Mock(return_value=SimpleNamespace(id=456))
service = MCPService(ap)
@@ -645,4 +643,4 @@ class TestMCPServiceTestMCPServer:
# Verify - load_mcp_server called
ap.tool_mgr.mcp_tool_loader.load_mcp_server.assert_called_once()
assert task_id == 456
assert task_id == 456
@@ -167,6 +167,7 @@ class TestLLMModelsServiceGetLLMModels:
mock_provider_result = _create_mock_result([])
call_count = 0
async def mock_execute(query):
return mock_result if call_count == 0 else mock_provider_result
@@ -200,6 +201,7 @@ class TestLLMModelsServiceGetLLMModels:
mock_provider_result = _create_mock_result([provider])
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -239,6 +241,7 @@ class TestLLMModelsServiceGetLLMModels:
mock_provider_result = _create_mock_result([provider])
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -279,6 +282,7 @@ class TestLLMModelsServiceGetLLMModel:
mock_provider_result = _create_mock_result([], first_item=provider)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -337,9 +341,7 @@ class TestLLMModelsServiceGetLLMModelsByProvider:
mock_result = _create_mock_result([model1, model2])
ap.persistence_mgr.execute_async = AsyncMock(return_value=mock_result)
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'model-1', 'name': 'Model 1'}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'model-1', 'name': 'Model 1'})
service = LLMModelsService(ap)
@@ -371,12 +373,14 @@ class TestLLMModelsServiceCreateLLMModel:
service = LLMModelsService(ap)
# Execute
model_uuid = await service.create_llm_model({
'name': 'New LLM',
'provider_uuid': 'provider-uuid',
'abilities': [],
'extra_args': {},
})
model_uuid = await service.create_llm_model(
{
'name': 'New LLM',
'provider_uuid': 'provider-uuid',
'abilities': [],
'extra_args': {},
}
)
# Verify
assert model_uuid is not None
@@ -400,13 +404,16 @@ class TestLLMModelsServiceCreateLLMModel:
service = LLMModelsService(ap)
# Execute
model_uuid = await service.create_llm_model({
'uuid': 'preserved-uuid',
'name': 'Preserved UUID Model',
'provider_uuid': 'provider-uuid',
'abilities': [],
'extra_args': {},
}, preserve_uuid=True)
model_uuid = await service.create_llm_model(
{
'uuid': 'preserved-uuid',
'name': 'Preserved UUID Model',
'provider_uuid': 'provider-uuid',
'abilities': [],
'extra_args': {},
},
preserve_uuid=True,
)
# Verify
assert model_uuid == 'preserved-uuid'
@@ -459,12 +466,14 @@ class TestLLMModelsServiceCreateLLMModel:
# Execute & Verify
with pytest.raises(Exception, match='provider not found'):
await service.create_llm_model({
'name': 'No Provider Model',
'provider_uuid': 'nonexistent-provider',
'abilities': [],
'extra_args': {},
})
await service.create_llm_model(
{
'name': 'No Provider Model',
'provider_uuid': 'nonexistent-provider',
'abilities': [],
'extra_args': {},
}
)
async def test_create_llm_model_with_provider_data(self):
"""Creates provider when provider data provided."""
@@ -490,16 +499,18 @@ class TestLLMModelsServiceCreateLLMModel:
service = LLMModelsService(ap)
# Execute - with provider data (no UUID)
result_uuid = await service.create_llm_model({
'name': 'Model with New Provider',
'provider': {
'requester': 'openai',
'base_url': 'https://api.openai.com',
'api_keys': ['key'],
},
'abilities': [],
'extra_args': {},
})
result_uuid = await service.create_llm_model(
{
'name': 'Model with New Provider',
'provider': {
'requester': 'openai',
'base_url': 'https://api.openai.com',
'api_keys': ['key'],
},
'abilities': [],
'extra_args': {},
}
)
# Verify - provider_service was called and UUID generated
ap.provider_service.find_or_create_provider.assert_called_once()
@@ -525,11 +536,14 @@ class TestLLMModelsServiceUpdateLLMModel:
service = LLMModelsService(ap)
# Execute
await service.update_llm_model('existing-uuid', {
'uuid': 'should-be-removed',
'name': 'Updated Name',
'provider_uuid': 'provider-uuid',
})
await service.update_llm_model(
'existing-uuid',
{
'uuid': 'should-be-removed',
'name': 'Updated Name',
'provider_uuid': 'provider-uuid',
},
)
# Verify - remove and load called
ap.model_mgr.remove_llm_model.assert_called_once_with('existing-uuid')
@@ -549,10 +563,13 @@ class TestLLMModelsServiceUpdateLLMModel:
# Execute & Verify
with pytest.raises(Exception, match='provider not found'):
await service.update_llm_model('model-uuid', {
'name': 'Update',
'provider_uuid': 'nonexistent-provider',
})
await service.update_llm_model(
'model-uuid',
{
'name': 'Update',
'provider_uuid': 'nonexistent-provider',
},
)
async def test_update_llm_model_reloads_context_length_as_column(self):
"""Updates runtime model with context_length outside extra_args."""
@@ -618,9 +635,7 @@ class TestEmbeddingModelsServiceGetEmbeddingModels:
mock_result = _create_mock_result([])
ap.persistence_mgr.execute_async = AsyncMock(return_value=mock_result)
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'embedding-uuid', 'name': 'Test'}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'embedding-uuid', 'name': 'Test'})
service = EmbeddingModelsService(ap)
@@ -643,6 +658,7 @@ class TestEmbeddingModelsServiceGetEmbeddingModels:
mock_provider_result = _create_mock_result([provider])
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -683,6 +699,7 @@ class TestEmbeddingModelsServiceGetEmbeddingModel:
mock_provider_result = _create_mock_result([], first_item=provider)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -742,11 +759,13 @@ class TestEmbeddingModelsServiceCreateEmbeddingModel:
service = EmbeddingModelsService(ap)
# Execute
model_uuid = await service.create_embedding_model({
'name': 'New Embedding',
'provider_uuid': 'provider-uuid',
'extra_args': {},
})
model_uuid = await service.create_embedding_model(
{
'name': 'New Embedding',
'provider_uuid': 'provider-uuid',
'extra_args': {},
}
)
# Verify
assert model_uuid is not None
@@ -767,11 +786,13 @@ class TestEmbeddingModelsServiceCreateEmbeddingModel:
# Execute & Verify
with pytest.raises(Exception, match='provider not found'):
await service.create_embedding_model({
'name': 'No Provider Embedding',
'provider_uuid': 'nonexistent',
'extra_args': {},
})
await service.create_embedding_model(
{
'name': 'No Provider Embedding',
'provider_uuid': 'nonexistent',
'extra_args': {},
}
)
class TestEmbeddingModelsServiceDeleteEmbeddingModel:
@@ -829,6 +850,7 @@ class TestRerankModelsServiceGetRerankModels:
mock_provider_result = _create_mock_result([provider])
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -869,6 +891,7 @@ class TestRerankModelsServiceGetRerankModel:
mock_provider_result = _create_mock_result([], first_item=provider)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -928,11 +951,13 @@ class TestRerankModelsServiceCreateRerankModel:
service = RerankModelsService(ap)
# Execute
model_uuid = await service.create_rerank_model({
'name': 'New Rerank',
'provider_uuid': 'provider-uuid',
'extra_args': {},
})
model_uuid = await service.create_rerank_model(
{
'name': 'New Rerank',
'provider_uuid': 'provider-uuid',
'extra_args': {},
}
)
# Verify
assert model_uuid is not None
@@ -952,11 +977,13 @@ class TestRerankModelsServiceCreateRerankModel:
# Execute & Verify
with pytest.raises(Exception, match='provider not found'):
await service.create_rerank_model({
'name': 'No Provider Rerank',
'provider_uuid': 'nonexistent',
'extra_args': {},
})
await service.create_rerank_model(
{
'name': 'No Provider Rerank',
'provider_uuid': 'nonexistent',
'extra_args': {},
}
)
class TestRerankModelsServiceDeleteRerankModel:
@@ -995,9 +1022,7 @@ class TestEmbeddingModelsServiceGetEmbeddingModelsByProvider:
mock_result = _create_mock_result([model1, model2])
ap.persistence_mgr.execute_async = AsyncMock(return_value=mock_result)
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'emb-1', 'name': 'Embedding 1'}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'emb-1', 'name': 'Embedding 1'})
service = EmbeddingModelsService(ap)
@@ -1022,9 +1047,7 @@ class TestRerankModelsServiceGetRerankModelsByProvider:
mock_result = _create_mock_result([model1, model2])
ap.persistence_mgr.execute_async = AsyncMock(return_value=mock_result)
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'rerank-1', 'name': 'Rerank 1'}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'rerank-1', 'name': 'Rerank 1'})
service = RerankModelsService(ap)
@@ -1042,14 +1065,10 @@ class TestValidateProviderSupports:
def _make_ap(requester_name: str, support_type):
"""Build a fake ap whose model_mgr resolves a manifest with support_type."""
manifest = SimpleNamespace(spec={'support_type': support_type})
runtime_provider = SimpleNamespace(
provider_entity=SimpleNamespace(requester=requester_name)
)
runtime_provider = SimpleNamespace(provider_entity=SimpleNamespace(requester=requester_name))
model_mgr = SimpleNamespace(
provider_dict={'p1': runtime_provider},
get_available_requester_manifest_by_name=lambda name: manifest
if name == requester_name
else None,
get_available_requester_manifest_by_name=lambda name: manifest if name == requester_name else None,
)
return SimpleNamespace(model_mgr=model_mgr)
@@ -1066,9 +1085,7 @@ class TestValidateProviderSupports:
async def test_allows_when_support_type_missing(self):
# Manifest without support_type must not block (backward compatible)
manifest = SimpleNamespace(spec={})
runtime_provider = SimpleNamespace(
provider_entity=SimpleNamespace(requester='legacy')
)
runtime_provider = SimpleNamespace(provider_entity=SimpleNamespace(requester='legacy'))
model_mgr = SimpleNamespace(
provider_dict={'p1': runtime_provider},
get_available_requester_manifest_by_name=lambda name: manifest,
@@ -215,13 +215,7 @@ class TestPipelineServiceCreatePipeline:
ap = SimpleNamespace()
ap.persistence_mgr = SimpleNamespace()
ap.instance_config = SimpleNamespace()
ap.instance_config.data = {
'system': {
'limitation': {
'max_pipelines': 2
}
}
}
ap.instance_config.data = {'system': {'limitation': {'max_pipelines': 2}}}
ap.pipeline_mgr = SimpleNamespace()
ap.pipeline_mgr.load_pipeline = AsyncMock()
ap.ver_mgr = SimpleNamespace()
@@ -229,9 +223,7 @@ class TestPipelineServiceCreatePipeline:
mock_result = _create_mock_result([_create_mock_pipeline(), _create_mock_pipeline()])
ap.persistence_mgr.execute_async = AsyncMock(return_value=mock_result)
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'uuid-1', 'name': 'Pipeline 1'}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'uuid-1', 'name': 'Pipeline 1'})
service = PipelineService(ap)
@@ -258,14 +250,14 @@ class TestPipelineServiceCreatePipeline:
# Mock persistence for insert
ap.persistence_mgr.execute_async = AsyncMock()
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'new-uuid', 'name': 'New Pipeline'}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'new-uuid', 'name': 'New Pipeline'})
# Mock the file read for default config - patch at the utils module level
default_config = {'trigger': {}, 'safety': {}, 'ai': {}, 'output': {}}
with patch('builtins.open', mock_open(read_data=json.dumps(default_config))):
with patch('langbot.pkg.utils.paths.get_resource_path', return_value='templates/default-pipeline-config.json'):
with patch(
'langbot.pkg.utils.paths.get_resource_path', return_value='templates/default-pipeline-config.json'
):
bot_uuid = await service.create_pipeline({'name': 'New Pipeline'})
# Verify
@@ -286,7 +278,9 @@ class TestPipelineServiceCreatePipeline:
service = PipelineService(ap)
service.get_pipelines = AsyncMock(return_value=[])
service.get_pipeline = AsyncMock(return_value={'uuid': 'new-uuid', 'name': 'Default Pipeline', 'is_default': True})
service.get_pipeline = AsyncMock(
return_value={'uuid': 'new-uuid', 'name': 'Default Pipeline', 'is_default': True}
)
ap.persistence_mgr.execute_async = AsyncMock()
ap.persistence_mgr.serialize_model = Mock(
@@ -296,7 +290,9 @@ class TestPipelineServiceCreatePipeline:
# Mock the file read
default_config = {}
with patch('builtins.open', mock_open(read_data=json.dumps(default_config))):
with patch('langbot.pkg.utils.paths.get_resource_path', return_value='templates/default-pipeline-config.json'):
with patch(
'langbot.pkg.utils.paths.get_resource_path', return_value='templates/default-pipeline-config.json'
):
await service.create_pipeline({'name': 'Default Pipeline'}, default=True)
# Verify - execute was called
@@ -316,10 +312,12 @@ class TestPipelineServiceCreatePipeline:
service = PipelineService(ap)
service.get_pipelines = AsyncMock(return_value=[])
service.get_pipeline = AsyncMock(return_value={
'uuid': 'new-uuid',
'extensions_preferences': {},
})
service.get_pipeline = AsyncMock(
return_value={
'uuid': 'new-uuid',
'extensions_preferences': {},
}
)
insert_params = []
@@ -339,7 +337,9 @@ class TestPipelineServiceCreatePipeline:
default_config = {}
with patch('builtins.open', mock_open(read_data=json.dumps(default_config))):
with patch('langbot.pkg.utils.paths.get_resource_path', return_value='templates/default-pipeline-config.json'):
with patch(
'langbot.pkg.utils.paths.get_resource_path', return_value='templates/default-pipeline-config.json'
):
await service.create_pipeline({'name': 'New Pipeline'})
assert len(insert_params) == 1
@@ -353,6 +353,7 @@ class TestPipelineServiceCreatePipeline:
class _MockResultWithBots:
"""Helper class to mock SQLAlchemy result with iterable .all() method."""
def __init__(self, bots_list):
self._bots_list = bots_list
@@ -428,6 +429,7 @@ class TestPipelineServiceUpdatePipeline:
# 1. UPDATE (line 125) - returns Mock (no result needed)
# 2. SELECT bots (line 136) - returns bot_result with .all()
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -528,13 +530,7 @@ class TestPipelineServiceCopyPipeline:
ap = SimpleNamespace()
ap.persistence_mgr = SimpleNamespace()
ap.instance_config = SimpleNamespace()
ap.instance_config.data = {
'system': {
'limitation': {
'max_pipelines': 2
}
}
}
ap.instance_config.data = {'system': {'limitation': {'max_pipelines': 2}}}
ap.pipeline_mgr = SimpleNamespace()
ap.pipeline_mgr.load_pipeline = AsyncMock()
ap.ver_mgr = SimpleNamespace()
@@ -542,10 +538,12 @@ class TestPipelineServiceCopyPipeline:
service = PipelineService(ap)
# Mock get_pipelines to return 2 pipelines
service.get_pipelines = AsyncMock(return_value=[
{'uuid': 'uuid-1', 'name': 'Pipeline 1'},
{'uuid': 'uuid-2', 'name': 'Pipeline 2'},
])
service.get_pipelines = AsyncMock(
return_value=[
{'uuid': 'uuid-1', 'name': 'Pipeline 1'},
{'uuid': 'uuid-2', 'name': 'Pipeline 2'},
]
)
# Execute & Verify
with pytest.raises(ValueError, match='Maximum number of pipelines'):
@@ -642,9 +640,7 @@ class TestPipelineServiceCopyPipeline:
service = PipelineService(ap)
service.get_pipelines = AsyncMock(return_value=[])
ap.persistence_mgr.execute_async = AsyncMock(return_value=_create_mock_result(first_item=original))
ap.persistence_mgr.serialize_model = Mock(
return_value={'uuid': 'copy-uuid', 'is_default': False}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'uuid': 'copy-uuid', 'is_default': False})
service.get_pipeline = AsyncMock(return_value={'uuid': 'copy-uuid', 'is_default': False})
@@ -681,11 +677,10 @@ class TestPipelineServiceUpdatePipelineExtensions:
ap.pipeline_mgr.remove_pipeline = AsyncMock()
ap.pipeline_mgr.load_pipeline = AsyncMock()
original_pipeline = _create_mock_pipeline(
extensions_preferences={'enable_all_plugins': True, 'plugins': []}
)
original_pipeline = _create_mock_pipeline(extensions_preferences={'enable_all_plugins': True, 'plugins': []})
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -700,7 +695,7 @@ class TestPipelineServiceUpdatePipelineExtensions:
'extensions_preferences': {
'enable_all_plugins': False,
'plugins': [{'plugin_uuid': 'plugin-1'}],
}
},
}
)
@@ -711,7 +706,7 @@ class TestPipelineServiceUpdatePipelineExtensions:
'extensions_preferences': {
'enable_all_plugins': False,
'plugins': [{'plugin_uuid': 'plugin-1'}],
}
},
}
)
@@ -738,6 +733,7 @@ class TestPipelineServiceUpdatePipelineExtensions:
original_pipeline = _create_mock_pipeline()
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -752,7 +748,7 @@ class TestPipelineServiceUpdatePipelineExtensions:
'extensions_preferences': {
'enable_all_mcp_servers': False,
'mcp_servers': ['mcp-server-1'],
}
},
}
)
@@ -794,6 +790,7 @@ class TestPipelineServiceUpdatePipelineExtensions:
)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -245,12 +245,14 @@ class TestModelProviderServiceCreateProvider:
service = ModelProviderService(ap)
# Execute
provider_uuid = await service.create_provider({
'name': 'New Provider',
'requester': 'openai',
'base_url': 'https://api.openai.com',
'api_keys': ['key'],
})
provider_uuid = await service.create_provider(
{
'name': 'New Provider',
'requester': 'openai',
'base_url': 'https://api.openai.com',
'api_keys': ['key'],
}
)
# Verify - UUID is generated
assert provider_uuid is not None
@@ -274,12 +276,14 @@ class TestModelProviderServiceCreateProvider:
service = ModelProviderService(ap)
# Execute
result_uuid = await service.create_provider({
'name': 'Runtime Provider',
'requester': 'openai',
'base_url': 'https://api.openai.com',
'api_keys': ['key'],
})
result_uuid = await service.create_provider(
{
'name': 'Runtime Provider',
'requester': 'openai',
'base_url': 'https://api.openai.com',
'api_keys': ['key'],
}
)
# Verify - provider added to runtime dict and UUID generated
ap.model_mgr.load_provider.assert_called_once()
@@ -302,10 +306,13 @@ class TestModelProviderServiceUpdateProvider:
service = ModelProviderService(ap)
# Execute
await service.update_provider('existing-uuid', {
'uuid': 'should-be-removed', # Will be removed
'name': 'Updated Name',
})
await service.update_provider(
'existing-uuid',
{
'uuid': 'should-be-removed', # Will be removed
'name': 'Updated Name',
},
)
# Verify - reload called
ap.model_mgr.reload_provider.assert_called_once_with('existing-uuid')
@@ -364,6 +371,7 @@ class TestModelProviderServiceDeleteProvider:
rerank_result.first = Mock(return_value=None)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -396,6 +404,7 @@ class TestModelProviderServiceDeleteProvider:
rerank_result.first = Mock(return_value=Mock(spec=RerankModel)) # Has rerank model
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -454,6 +463,7 @@ class TestModelProviderServiceGetProviderModelCounts:
rerank_result.scalar = Mock(return_value=1)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -637,9 +647,7 @@ class TestModelProviderServiceUpdateSpaceModelProviderApiKeys:
await service.update_space_model_provider_api_keys('space-api-key')
# Verify - update and reload called for Space provider UUID
ap.model_mgr.reload_provider.assert_called_once_with(
'00000000-0000-0000-0000-000000000000'
)
ap.model_mgr.reload_provider.assert_called_once_with('00000000-0000-0000-0000-000000000000')
class TestModelProviderServiceScanProviderModels:
@@ -795,9 +803,7 @@ class TestModelProviderServiceScanProviderModels:
runtime_provider.token_mgr = Mock()
runtime_provider.token_mgr.get_token = Mock(return_value='token')
runtime_provider.token_mgr.tokens = ['token']
runtime_provider.requester.scan_models = AsyncMock(
side_effect=NotImplementedError('scan not supported')
)
runtime_provider.requester.scan_models = AsyncMock(side_effect=NotImplementedError('scan not supported'))
ap.model_mgr.load_provider = AsyncMock(return_value=runtime_provider)
service = ModelProviderService(ap)
@@ -848,9 +854,7 @@ class TestModelProviderServiceScanProviderModels:
ap.model_mgr.load_provider = AsyncMock(return_value=runtime_provider)
# Mock existing LLM model
ap.llm_model_service.get_llm_models_by_provider = AsyncMock(
return_value=[{'name': 'Existing Model'}]
)
ap.llm_model_service.get_llm_models_by_provider = AsyncMock(return_value=[{'name': 'Existing Model'}])
ap.embedding_models_service.get_embedding_models_by_provider = AsyncMock(return_value=[])
service = ModelProviderService(ap)
@@ -863,4 +867,4 @@ class TestModelProviderServiceScanProviderModels:
assert existing_model['already_added'] is True
new_model = next(m for m in result['models'] if m['name'] == 'New Model')
assert new_model['already_added'] is False
assert new_model['already_added'] is False
@@ -393,14 +393,16 @@ class TestSpaceServiceRefreshToken:
# Mock HTTP response
mock_response = MagicMock()
mock_response.status = 200
mock_response.json = AsyncMock(return_value={
'code': 0,
'data': {
'access_token': 'new_access_token',
'refresh_token': 'new_refresh_token',
'expires_in': 3600,
mock_response.json = AsyncMock(
return_value={
'code': 0,
'data': {
'access_token': 'new_access_token',
'refresh_token': 'new_refresh_token',
'expires_in': 3600,
},
}
})
)
with patch('langbot.pkg.api.http.service.space.httpclient.get_session') as mock_session:
mock_session_obj = MagicMock()
@@ -429,10 +431,12 @@ class TestSpaceServiceRefreshToken:
# Mock HTTP response with error
mock_response = MagicMock()
mock_response.status = 200
mock_response.json = AsyncMock(return_value={
'code': 1,
'msg': 'Invalid refresh token',
})
mock_response.json = AsyncMock(
return_value={
'code': 1,
'msg': 'Invalid refresh token',
}
)
mock_response.text = AsyncMock(return_value='{"code":1,"msg":"Invalid refresh token"}')
with patch('langbot.pkg.api.http.service.space.httpclient.get_session') as mock_session:
@@ -489,14 +493,16 @@ class TestSpaceServiceExchangeOAuthCode:
# Mock HTTP response
mock_response = MagicMock()
mock_response.status = 200
mock_response.json = AsyncMock(return_value={
'code': 0,
'data': {
'access_token': 'new_access_token',
'refresh_token': 'new_refresh_token',
'expires_in': 3600,
mock_response.json = AsyncMock(
return_value={
'code': 0,
'data': {
'access_token': 'new_access_token',
'refresh_token': 'new_refresh_token',
'expires_in': 3600,
},
}
})
)
with patch('langbot.pkg.api.http.service.space.httpclient.get_session') as mock_session:
mock_session_obj = MagicMock()
@@ -555,13 +561,15 @@ class TestSpaceServiceGetUserInfoRaw:
# Mock HTTP response
mock_response = MagicMock()
mock_response.status = 200
mock_response.json = AsyncMock(return_value={
'code': 0,
'data': {
'email': 'test@example.com',
'credits': 100,
mock_response.json = AsyncMock(
return_value={
'code': 0,
'data': {
'email': 'test@example.com',
'credits': 100,
},
}
})
)
with patch('langbot.pkg.api.http.service.space.httpclient.get_session') as mock_session:
mock_session_obj = MagicMock()
@@ -669,27 +677,29 @@ class TestSpaceServiceGetModels:
# Mock HTTP response with proper model data matching SpaceModel schema
mock_response = MagicMock()
mock_response.status = 200
mock_response.json = AsyncMock(return_value={
'code': 0,
'data': {
'models': [
{
'uuid': 'uuid-1',
'model_id': 'model-1',
'provider': 'provider-1',
'category': 'chat',
'status': 'active',
},
{
'uuid': 'uuid-2',
'model_id': 'model-2',
'provider': 'provider-2',
'category': 'chat',
'status': 'active',
},
]
mock_response.json = AsyncMock(
return_value={
'code': 0,
'data': {
'models': [
{
'uuid': 'uuid-1',
'model_id': 'model-1',
'provider': 'provider-1',
'category': 'chat',
'status': 'active',
},
{
'uuid': 'uuid-2',
'model_id': 'model-2',
'provider': 'provider-2',
'category': 'chat',
'status': 'active',
},
]
},
}
})
)
with patch('langbot.pkg.api.http.service.space.httpclient.get_session') as mock_session:
mock_session_obj = MagicMock()
@@ -775,4 +785,4 @@ class TestSpaceServiceCreditsCache:
# Verify - cache updated
assert result == 500
assert 'test@example.com' in service._credits_cache
assert service._credits_cache['test@example.com'][0] == 500
assert service._credits_cache['test@example.com'][0] == 500
@@ -495,6 +495,7 @@ class TestUserServiceCreateOrUpdateSpaceUser:
# First call (line 138) returns None, second call (line 194) returns new_user
call_count = 0
async def mock_get_by_space_uuid(uuid):
nonlocal call_count
call_count += 1
@@ -565,6 +566,7 @@ class TestUserServiceCreateOrUpdateSpaceUser:
# First call (line 138) returns None, second call (line 194) returns new_user
call_count = 0
async def mock_get_by_space_uuid(uuid):
nonlocal call_count
call_count += 1
@@ -605,4 +607,4 @@ class TestUserServiceCreateUserLock:
# Verify lock exists
assert hasattr(service, '_create_user_lock')
assert service._create_user_lock is not None
assert service._create_user_lock is not None
@@ -132,6 +132,7 @@ class TestWebhookServiceCreateWebhook:
# execute_async returns different results
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -181,6 +182,7 @@ class TestWebhookServiceCreateWebhook:
)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -217,6 +219,7 @@ class TestWebhookServiceCreateWebhook:
created_webhook = _create_mock_webhook(webhook_id=1, enabled=False)
call_count = 0
async def mock_execute(query):
nonlocal call_count
call_count += 1
@@ -225,9 +228,7 @@ class TestWebhookServiceCreateWebhook:
return _create_mock_result(first_item=created_webhook)
ap.persistence_mgr.execute_async = AsyncMock(side_effect=mock_execute)
ap.persistence_mgr.serialize_model = Mock(
return_value={'id': 1, 'enabled': False}
)
ap.persistence_mgr.serialize_model = Mock(return_value={'id': 1, 'enabled': False})
service = WebhookService(ap)
@@ -503,4 +504,4 @@ class TestWebhookServiceGetEnabledWebhooks:
result = await service.get_enabled_webhooks()
# Verify - should be empty (SQL would filter disabled)
assert result == []
assert result == []