Files
LangBot/tests/unit_tests/rag/test_kbmgr.py
T
2026-07-19 09:58:59 +08:00

825 lines
28 KiB
Python

"""Tests for Workspace-scoped RAG manager and runtime knowledge bases."""
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock
import pytest
from langbot.pkg.api.http.context import ExecutionContext
from langbot.pkg.entity.persistence.rag import KnowledgeBase
from langbot.pkg.rag.knowledge.kbmgr import RAGManager, RuntimeKnowledgeBase
from langbot.pkg.workspace.errors import WorkspaceNotFoundError
CONTEXT_A = ExecutionContext(
instance_uuid='instance-a',
workspace_uuid='workspace-a',
placement_generation=5,
)
CONTEXT_B = ExecutionContext(
instance_uuid='instance-a',
workspace_uuid='workspace-b',
placement_generation=5,
)
class _Result:
def __init__(self, rows=(), *, first=None):
self.rows = list(rows)
self._first = first
def all(self):
return self.rows
def first(self):
return self._first
def _entity(*, kb_uuid='kb-a', workspace_uuid='workspace-a', plugin_id='author/engine'):
return KnowledgeBase(
uuid=kb_uuid,
workspace_uuid=workspace_uuid,
name='Test KB',
description='description',
knowledge_engine_plugin_id=plugin_id,
collection_id=kb_uuid,
creation_settings={},
retrieval_settings={},
)
def _app():
return SimpleNamespace(
logger=Mock(),
persistence_mgr=SimpleNamespace(
execute_async=AsyncMock(return_value=_Result()),
serialize_model=Mock(
side_effect=lambda _model, row: {
'uuid': row.uuid,
'workspace_uuid': row.workspace_uuid,
'name': row.name,
'description': row.description,
'knowledge_engine_plugin_id': row.knowledge_engine_plugin_id,
'collection_id': row.collection_id,
'creation_settings': row.creation_settings,
'retrieval_settings': row.retrieval_settings,
}
),
),
plugin_connector=SimpleNamespace(
is_enable_plugin=True,
require_workspace_context=AsyncMock(side_effect=lambda context: context),
list_knowledge_engines=AsyncMock(
return_value=[
{
'plugin_id': 'author/engine',
'name': {'en_US': 'Engine'},
'capabilities': ['doc_ingestion'],
}
]
),
rag_on_kb_create=AsyncMock(),
rag_on_kb_delete=AsyncMock(),
call_rag_ingest=AsyncMock(return_value={'status': 'success'}),
call_rag_retrieve=AsyncMock(return_value={'results': []}),
call_rag_delete_document=AsyncMock(return_value=True),
call_parser=AsyncMock(),
),
workspace_service=SimpleNamespace(
get_execution_binding=AsyncMock(
side_effect=lambda workspace_uuid, **_kwargs: SimpleNamespace(
instance_uuid='instance-a',
workspace_uuid=workspace_uuid,
placement_generation=5,
)
)
),
storage_mgr=SimpleNamespace(storage_provider=AsyncMock()),
task_mgr=SimpleNamespace(create_user_task=Mock(return_value=SimpleNamespace(id='task-a'))),
)
@pytest.mark.asyncio
async def test_create_binds_workspace_and_uses_tuple_runtime_key():
app = _app()
manager = RAGManager(app)
kb = await manager.create_knowledge_base(
CONTEXT_A,
name='Created',
knowledge_engine_plugin_id='author/engine',
creation_settings={'model': 'embedding-a'},
)
assert kb.workspace_uuid == 'workspace-a'
assert ('workspace-a', kb.uuid) in manager.knowledge_bases
app.plugin_connector.rag_on_kb_create.assert_awaited_once_with(
'author/engine',
kb.uuid,
{'model': 'embedding-a'},
)
@pytest.mark.asyncio
async def test_create_rejects_unknown_engine_and_rolls_back_plugin_failure():
app = _app()
manager = RAGManager(app)
app.plugin_connector.list_knowledge_engines.return_value = []
with pytest.raises(ValueError, match='not found'):
await manager.create_knowledge_base(
CONTEXT_A,
name='Unknown',
knowledge_engine_plugin_id='missing/engine',
creation_settings={},
)
app.plugin_connector.list_knowledge_engines.return_value = [{'plugin_id': 'author/engine'}]
app.plugin_connector.rag_on_kb_create.side_effect = RuntimeError('plugin failed')
with pytest.raises(RuntimeError, match='plugin failed'):
await manager.create_knowledge_base(
CONTEXT_A,
name='Rollback',
knowledge_engine_plugin_id='author/engine',
creation_settings={},
)
assert manager.knowledge_bases == {}
@pytest.mark.asyncio
async def test_runtime_retrieve_carries_context_and_merges_settings_without_mutation():
app = _app()
entity = _entity()
entity.retrieval_settings = {'top_k': 10, 'model': 'default'}
app.plugin_connector.call_rag_retrieve.return_value = {
'results': [
{
'id': 'entry-a',
'content': [{'type': 'text', 'text': 'hello'}],
'metadata': {},
'distance': 0.2,
}
]
}
runtime = RuntimeKnowledgeBase(app, entity, CONTEXT_A)
overrides = {'top_k': 2, 'filters': {'file_id': 'file-a'}}
results = await runtime.retrieve(CONTEXT_A, 'query', settings=overrides)
assert results[0].id == 'entry-a'
assert overrides == {'top_k': 2, 'filters': {'file_id': 'file-a'}}
payload = app.plugin_connector.call_rag_retrieve.await_args.args[1]
assert payload['knowledge_base_id'] == 'kb-a'
assert payload['collection_id'] == 'kb-a'
assert payload['retrieval_settings']['top_k'] == 2
assert payload['filters'] == {'file_id': 'file-a'}
@pytest.mark.asyncio
async def test_runtime_rejects_cross_workspace_and_stale_contexts():
app = _app()
runtime = RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)
with pytest.raises(WorkspaceNotFoundError):
await runtime.retrieve(CONTEXT_B, 'query')
stale = CONTEXT_A.__class__(
instance_uuid='instance-a',
workspace_uuid='workspace-a',
placement_generation=4,
)
with pytest.raises(WorkspaceNotFoundError):
await runtime.retrieve(stale, 'query')
app.plugin_connector.call_rag_retrieve.assert_not_awaited()
@pytest.mark.asyncio
async def test_runtime_retrieve_fails_before_bound_connector_on_generation_mismatch():
app = _app()
app.plugin_connector.require_workspace_context.side_effect = WorkspaceNotFoundError('Plugin resource not found')
runtime = RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)
with pytest.raises(WorkspaceNotFoundError, match='Plugin resource not found'):
await runtime.retrieve(CONTEXT_A, 'query')
app.plugin_connector.call_rag_retrieve.assert_not_awaited()
@pytest.mark.asyncio
@pytest.mark.parametrize(
('operation', 'connector_method'),
[
('create', 'rag_on_kb_create'),
('ingest', 'call_rag_ingest'),
('delete', 'call_rag_delete_document'),
],
)
async def test_runtime_plugin_mutations_fail_before_mismatched_connector(operation, connector_method):
app = _app()
app.plugin_connector.require_workspace_context.side_effect = WorkspaceNotFoundError('Plugin resource not found')
runtime = RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)
with pytest.raises(WorkspaceNotFoundError, match='Plugin resource not found'):
if operation == 'create':
await runtime._on_kb_create(CONTEXT_A)
elif operation == 'ingest':
await runtime._ingest_document(CONTEXT_A, {'filename': 'document.pdf'}, 'storage/path')
else:
await runtime._delete_document(CONTEXT_A, 'file-a')
getattr(app.plugin_connector, connector_method).assert_not_awaited()
@pytest.mark.asyncio
async def test_create_fails_before_engine_discovery_on_connector_workspace_mismatch():
app = _app()
app.plugin_connector.require_workspace_context.side_effect = WorkspaceNotFoundError('Plugin resource not found')
manager = RAGManager(app)
with pytest.raises(WorkspaceNotFoundError, match='Plugin resource not found'):
await manager.create_knowledge_base(
CONTEXT_A,
name='Other workspace',
knowledge_engine_plugin_id='author/engine',
creation_settings={},
)
app.plugin_connector.list_knowledge_engines.assert_not_awaited()
app.persistence_mgr.execute_async.assert_not_awaited()
@pytest.mark.asyncio
async def test_ingestion_payload_uses_host_owned_kb_collection():
app = _app()
runtime = RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)
metadata = {'filename': 'document.pdf'}
await runtime._ingest_document(CONTEXT_A, metadata, 'uploads/document.pdf')
payload = app.plugin_connector.call_rag_ingest.await_args.args[1]
assert payload['knowledge_base_id'] == 'kb-a'
assert payload['collection_id'] == 'kb-a'
assert payload['file_object']['metadata']['knowledge_base_id'] == 'kb-a'
@pytest.mark.asyncio
async def test_delete_file_checks_workspace_and_parent_before_plugin_call():
app = _app()
runtime = RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)
app.persistence_mgr.execute_async.return_value = _Result(first=('file-a',))
await runtime.delete_file(CONTEXT_A, 'file-a')
app.plugin_connector.call_rag_delete_document.assert_awaited_once_with(
'author/engine',
'file-a',
'kb-a',
)
missing_app = _app()
missing = RuntimeKnowledgeBase(missing_app, _entity(), CONTEXT_A)
with pytest.raises(WorkspaceNotFoundError):
await missing.delete_file(CONTEXT_A, 'file-other')
missing_app.plugin_connector.call_rag_delete_document.assert_not_awaited()
@pytest.mark.asyncio
async def test_manager_get_remove_and_delete_are_workspace_scoped():
app = _app()
manager = RAGManager(app)
runtime = await manager.load_knowledge_base(CONTEXT_A, _entity())
assert await manager.get_knowledge_base_by_uuid(CONTEXT_A, 'kb-a') is runtime
assert await manager.get_knowledge_base_by_uuid(CONTEXT_B, 'kb-a') is None
await manager.remove_knowledge_base_from_runtime(CONTEXT_B, 'kb-a')
assert await manager.get_knowledge_base_by_uuid(CONTEXT_A, 'kb-a') is runtime
await manager.delete_knowledge_base(CONTEXT_A, 'kb-a')
app.plugin_connector.rag_on_kb_delete.assert_awaited_once()
assert await manager.get_knowledge_base_by_uuid(CONTEXT_A, 'kb-a') is None
@pytest.mark.asyncio
async def test_details_queries_require_workspace_and_enrich_engine():
app = _app()
row_a = _entity()
app.persistence_mgr.execute_async.return_value = _Result([row_a], first=row_a)
manager = RAGManager(app)
listed = await manager.get_all_knowledge_base_details(CONTEXT_A)
fetched = await manager.get_knowledge_base_details(CONTEXT_A, 'kb-a')
assert listed[0]['knowledge_engine']['plugin_id'] == 'author/engine'
assert fetched['knowledge_engine']['capabilities'] == ['doc_ingestion']
@pytest.mark.asyncio
async def test_load_dict_filters_computed_fields_and_requires_matching_workspace():
app = _app()
manager = RAGManager(app)
runtime = await manager.load_knowledge_base(
CONTEXT_A,
{
'uuid': 'kb-a',
'workspace_uuid': 'workspace-a',
'name': 'KB',
'description': '',
'knowledge_engine_plugin_id': 'author/engine',
'collection_id': 'kb-a',
'creation_settings': {},
'retrieval_settings': {},
'knowledge_engine': {'computed': True},
},
)
assert runtime.get_uuid() == 'kb-a'
with pytest.raises(WorkspaceNotFoundError):
await manager.load_knowledge_base(CONTEXT_B, _entity())
# Preserve the complete pre-tenancy RAG manager regression matrix. Every
# runtime operation now carries the immutable ExecutionContext, and cache
# assertions use the Workspace + resource tuple key introduced for isolation.
class TestRAGManagerCreateKnowledgeBase:
@pytest.mark.asyncio
async def test_creates_kb_with_valid_engine(self):
app = _app()
manager = RAGManager(app)
kb = await manager.create_knowledge_base(
CONTEXT_A,
name='Test KB',
knowledge_engine_plugin_id='author/engine',
creation_settings={'model': 'test'},
)
assert kb.name == 'Test KB'
assert kb.workspace_uuid == CONTEXT_A.workspace_uuid
assert kb.knowledge_engine_plugin_id == 'author/engine'
@pytest.mark.asyncio
async def test_raises_when_engine_not_found(self):
app = _app()
app.plugin_connector.list_knowledge_engines.return_value = []
with pytest.raises(ValueError, match='not found'):
await RAGManager(app).create_knowledge_base(
CONTEXT_A,
name='Test KB',
knowledge_engine_plugin_id='unknown/engine',
creation_settings={},
)
@pytest.mark.asyncio
async def test_rollback_on_plugin_create_failure(self):
app = _app()
app.plugin_connector.rag_on_kb_create.side_effect = RuntimeError('Plugin error')
manager = RAGManager(app)
with pytest.raises(RuntimeError, match='Plugin error'):
await manager.create_knowledge_base(
CONTEXT_A,
name='Test KB',
knowledge_engine_plugin_id='author/engine',
creation_settings={},
)
assert manager.knowledge_bases == {}
assert app.persistence_mgr.execute_async.await_count == 2
@pytest.mark.asyncio
async def test_sets_default_retrieval_settings(self):
app = _app()
kb = await RAGManager(app).create_knowledge_base(
CONTEXT_A,
name='Test KB',
knowledge_engine_plugin_id='author/engine',
creation_settings={},
retrieval_settings=None,
)
assert kb.retrieval_settings == {}
@pytest.mark.asyncio
async def test_skips_validation_when_plugin_disabled(self):
app = _app()
app.plugin_connector.is_enable_plugin = False
kb = await RAGManager(app).create_knowledge_base(
CONTEXT_A,
name='Test KB',
knowledge_engine_plugin_id='any/engine',
creation_settings={},
)
assert kb.knowledge_engine_plugin_id == 'any/engine'
app.plugin_connector.list_knowledge_engines.assert_not_awaited()
class TestRuntimeKnowledgeBaseOnKBCreate:
@pytest.mark.asyncio
async def test_calls_plugin_on_create(self):
app = _app()
entity = _entity()
entity.creation_settings = {'model': 'test'}
await RuntimeKnowledgeBase(app, entity, CONTEXT_A)._on_kb_create(CONTEXT_A)
app.plugin_connector.rag_on_kb_create.assert_awaited_once_with(
'author/engine',
entity.uuid,
{'model': 'test'},
)
@pytest.mark.asyncio
async def test_skips_when_no_plugin_id(self):
app = _app()
await RuntimeKnowledgeBase(
app,
_entity(plugin_id=None),
CONTEXT_A,
)._on_kb_create(CONTEXT_A)
app.plugin_connector.rag_on_kb_create.assert_not_awaited()
@pytest.mark.asyncio
async def test_raises_on_plugin_error(self):
app = _app()
app.plugin_connector.rag_on_kb_create.side_effect = RuntimeError('Plugin failed')
with pytest.raises(RuntimeError, match='Plugin failed'):
await RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)._on_kb_create(CONTEXT_A)
class TestRuntimeKnowledgeBaseDeleteFile:
@pytest.mark.asyncio
async def test_delete_file_calls_plugin_and_db(self):
app = _app()
app.persistence_mgr.execute_async.return_value = _Result(first=('file-uuid',))
await RuntimeKnowledgeBase(app, _entity(), CONTEXT_A).delete_file(
CONTEXT_A,
'file-uuid',
)
app.plugin_connector.call_rag_delete_document.assert_awaited_once_with(
'author/engine',
'file-uuid',
'kb-a',
)
assert app.persistence_mgr.execute_async.await_count == 2
class TestRuntimeKnowledgeBaseIngestDocument:
@pytest.mark.asyncio
async def test_ingest_calls_plugin(self):
app = _app()
result = await RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)._ingest_document(
CONTEXT_A,
{'filename': 'test.pdf'},
'storage/path',
)
assert result == {'status': 'success'}
app.plugin_connector.call_rag_ingest.assert_awaited_once()
@pytest.mark.asyncio
async def test_ingest_raises_when_no_plugin_id(self):
app = _app()
with pytest.raises(ValueError, match='Plugin ID required'):
await RuntimeKnowledgeBase(
app,
_entity(plugin_id=None),
CONTEXT_A,
)._ingest_document(CONTEXT_A, {'filename': 'test.pdf'}, 'path')
class TestRAGManagerLoadKnowledgeBasesFromDB:
@pytest.mark.asyncio
async def test_loads_all_kbs_from_db(self):
app = _app()
kb1 = _entity(kb_uuid='kb-1')
kb2 = _entity(kb_uuid='kb-2')
app.persistence_mgr.execute_async.return_value = _Result([kb1, kb2])
manager = RAGManager(app)
await manager.load_knowledge_bases_from_db()
assert set(manager.knowledge_bases) == {
('workspace-a', 'kb-1'),
('workspace-a', 'kb-2'),
}
@pytest.mark.asyncio
async def test_handles_load_error_gracefully(self):
app = _app()
app.persistence_mgr.execute_async.return_value = _Result([_entity()])
app.workspace_service.get_execution_binding.side_effect = RuntimeError('binding unavailable')
manager = RAGManager(app)
await manager.load_knowledge_bases_from_db()
assert manager.knowledge_bases == {}
app.logger.error.assert_called_once()
class TestRuntimeKnowledgeBaseGetters:
def test_get_uuid_returns_entity_uuid(self):
entity = _entity()
assert RuntimeKnowledgeBase(_app(), entity, CONTEXT_A).get_uuid() == entity.uuid
def test_get_name_returns_entity_name(self):
entity = _entity()
assert RuntimeKnowledgeBase(_app(), entity, CONTEXT_A).get_name() == entity.name
def test_get_knowledge_engine_plugin_id_returns_plugin_id(self):
runtime = RuntimeKnowledgeBase(_app(), _entity(), CONTEXT_A)
assert runtime.get_knowledge_engine_plugin_id() == 'author/engine'
def test_get_knowledge_engine_plugin_id_returns_empty_when_none(self):
runtime = RuntimeKnowledgeBase(_app(), _entity(plugin_id=None), CONTEXT_A)
assert runtime.get_knowledge_engine_plugin_id() == ''
class TestRuntimeKnowledgeBaseRetrieve:
@pytest.mark.asyncio
async def test_retrieve_merges_settings(self):
app = _app()
entity = _entity()
entity.retrieval_settings = {'top_k': 10, 'model': 'default'}
app.plugin_connector.call_rag_retrieve.return_value = {
'results': [
{
'id': 'doc1',
'content': [{'type': 'text', 'text': 'test content'}],
'metadata': {},
'distance': 0.1,
}
]
}
results = await RuntimeKnowledgeBase(app, entity, CONTEXT_A).retrieve(
CONTEXT_A,
'query text',
settings={'top_k': 20},
)
assert len(results) == 1
payload = app.plugin_connector.call_rag_retrieve.await_args.args[1]
assert payload['retrieval_settings'] == {'top_k': 20, 'model': 'default'}
@pytest.mark.asyncio
async def test_retrieve_adds_default_top_k(self):
app = _app()
await RuntimeKnowledgeBase(app, _entity(), CONTEXT_A).retrieve(
CONTEXT_A,
'query text',
)
payload = app.plugin_connector.call_rag_retrieve.await_args.args[1]
assert payload['retrieval_settings']['top_k'] == 5
@pytest.mark.asyncio
async def test_retrieve_converts_dict_to_entry(self):
app = _app()
app.plugin_connector.call_rag_retrieve.return_value = {
'results': [
{
'id': 'doc1',
'content': [{'type': 'text', 'text': 'test content'}],
'metadata': {'source': 'file.pdf'},
'distance': 0.15,
}
]
}
results = await RuntimeKnowledgeBase(app, _entity(), CONTEXT_A).retrieve(
CONTEXT_A,
'query',
)
assert len(results) == 1
assert results[0].id == 'doc1'
assert hasattr(results[0], 'content')
class TestRuntimeKnowledgeBaseDispose:
@pytest.mark.asyncio
async def test_dispose_calls_on_kb_delete(self):
app = _app()
await RuntimeKnowledgeBase(app, _entity(), CONTEXT_A).dispose(CONTEXT_A)
app.plugin_connector.rag_on_kb_delete.assert_awaited_once_with(
'author/engine',
'kb-a',
)
@pytest.mark.asyncio
async def test_dispose_skips_when_no_plugin_id(self):
app = _app()
await RuntimeKnowledgeBase(
app,
_entity(plugin_id=None),
CONTEXT_A,
).dispose(CONTEXT_A)
app.plugin_connector.rag_on_kb_delete.assert_not_awaited()
class TestRAGManagerInit:
def test_init_stores_app_reference(self):
app = _app()
assert RAGManager(app).ap is app
def test_init_creates_empty_knowledge_bases_dict(self):
assert RAGManager(_app()).knowledge_bases == {}
class TestRAGManagerGetKnowledgeBase:
@pytest.mark.asyncio
async def test_get_knowledge_base_by_uuid_returns_runtime_kb(self):
app = _app()
manager = RAGManager(app)
runtime = RuntimeKnowledgeBase(app, _entity(), CONTEXT_A)
manager.knowledge_bases[('workspace-a', 'kb-a')] = runtime
result = await manager.get_knowledge_base_by_uuid(CONTEXT_A, 'kb-a')
assert result is runtime
@pytest.mark.asyncio
async def test_get_knowledge_base_by_uuid_returns_none_when_not_found(self):
assert (
await RAGManager(_app()).get_knowledge_base_by_uuid(
CONTEXT_A,
'nonexistent-uuid',
)
is None
)
@pytest.mark.asyncio
async def test_remove_knowledge_base_from_runtime(self):
app = _app()
manager = RAGManager(app)
manager.knowledge_bases[('workspace-a', 'kb-a')] = RuntimeKnowledgeBase(
app,
_entity(),
CONTEXT_A,
)
await manager.remove_knowledge_base_from_runtime(CONTEXT_A, 'kb-a')
assert ('workspace-a', 'kb-a') not in manager.knowledge_bases
class TestRAGManagerEnrichKB:
def test_enrich_adds_engine_info_from_map(self):
kb_dict = {'knowledge_engine_plugin_id': 'author/engine'}
engine_map = {
'author/engine': {
'plugin_id': 'author/engine',
'name': 'Test Engine',
'capabilities': ['doc_ingestion', 'search'],
}
}
RAGManager(_app())._enrich_kb_dict(kb_dict, engine_map)
assert kb_dict['knowledge_engine']['plugin_id'] == 'author/engine'
assert kb_dict['knowledge_engine']['capabilities'] == ['doc_ingestion', 'search']
def test_enrich_uses_fallback_when_engine_not_in_map(self):
kb_dict = {'knowledge_engine_plugin_id': 'unknown/engine'}
RAGManager(_app())._enrich_kb_dict(kb_dict, {})
assert kb_dict['knowledge_engine']['plugin_id'] == 'unknown/engine'
assert kb_dict['knowledge_engine']['capabilities'] == []
def test_enrich_uses_fallback_when_no_plugin_id(self):
kb_dict = {}
RAGManager(_app())._enrich_kb_dict(kb_dict, {})
assert kb_dict['knowledge_engine']['plugin_id'] is None
assert 'en_US' in kb_dict['knowledge_engine']['name']
def test_enrich_converts_string_name_to_i18n(self):
kb_dict = {'knowledge_engine_plugin_id': 'author/engine'}
engine_map = {
'author/engine': {
'plugin_id': 'author/engine',
'name': 'Simple Name',
'capabilities': [],
}
}
RAGManager(_app())._enrich_kb_dict(kb_dict, engine_map)
assert kb_dict['knowledge_engine']['name'] == {
'en_US': 'Simple Name',
'zh_Hans': 'Simple Name',
}
class TestRAGManagerDeleteKnowledgeBase:
@pytest.mark.asyncio
async def test_delete_removes_from_runtime_and_disposes(self):
app = _app()
manager = RAGManager(app)
manager.knowledge_bases[('workspace-a', 'kb-a')] = RuntimeKnowledgeBase(
app,
_entity(),
CONTEXT_A,
)
await manager.delete_knowledge_base(CONTEXT_A, 'kb-a')
assert ('workspace-a', 'kb-a') not in manager.knowledge_bases
app.plugin_connector.rag_on_kb_delete.assert_awaited_once()
@pytest.mark.asyncio
async def test_delete_logs_warning_when_not_in_runtime(self):
app = _app()
await RAGManager(app).delete_knowledge_base(CONTEXT_A, 'nonexistent-uuid')
app.logger.warning.assert_called_once()
class TestRAGManagerGetAllDetails:
@pytest.mark.asyncio
async def test_returns_empty_list_when_no_kbs(self):
assert await RAGManager(_app()).get_all_knowledge_base_details(CONTEXT_A) == []
@pytest.mark.asyncio
async def test_enriches_each_kb_with_engine_info(self):
app = _app()
app.persistence_mgr.execute_async.return_value = _Result([_entity()])
result = await RAGManager(app).get_all_knowledge_base_details(CONTEXT_A)
assert len(result) == 1
assert result[0]['knowledge_engine']['plugin_id'] == 'author/engine'
class TestRAGManagerGetDetails:
@pytest.mark.asyncio
async def test_returns_none_when_kb_not_found(self):
assert (
await RAGManager(_app()).get_knowledge_base_details(
CONTEXT_A,
'nonexistent',
)
is None
)
@pytest.mark.asyncio
async def test_returns_enriched_kb_dict(self):
app = _app()
app.persistence_mgr.execute_async.return_value = _Result(first=_entity())
result = await RAGManager(app).get_knowledge_base_details(CONTEXT_A, 'kb-a')
assert result is not None
assert result['knowledge_engine']['plugin_id'] == 'author/engine'
class TestRAGManagerLoadKnowledgeBase:
@pytest.mark.asyncio
async def test_loads_kb_entity_into_runtime(self):
manager = RAGManager(_app())
result = await manager.load_knowledge_base(CONTEXT_A, _entity())
assert ('workspace-a', 'kb-a') in manager.knowledge_bases
assert result.get_uuid() == 'kb-a'
@pytest.mark.asyncio
async def test_load_handles_dict_entity(self):
manager = RAGManager(_app())
kb_dict = {
'uuid': 'kb-uuid',
'workspace_uuid': 'workspace-a',
'name': 'Test',
'description': '',
'knowledge_engine_plugin_id': 'author/engine',
'collection_id': 'kb-uuid',
'creation_settings': {},
'retrieval_settings': {},
'knowledge_engine': {'name': 'should_be_filtered'},
}
await manager.load_knowledge_base(CONTEXT_A, kb_dict)
assert ('workspace-a', 'kb-uuid') in manager.knowledge_bases