Files
LangBot/tests/unit_tests/vector/test_mgr.py
huanghuoguoguo 70ec75f9a2 feat(test): Phase 1.5 coverage expansion - COV-001 to COV-013
Coverage baseline raised from 13.65% to 26% (+12.35%)
Gate raised from 12% to 18%

Tasks completed:
- COV-001: Command system unit tests (100% coverage)
- COV-002: API service unit tests batch 1 (user/apikey/model/provider)
- COV-003: Provider model manager unit tests
- COV-004: Pipeline remaining stage tests (aggregator/cntfilter/longtext/msgtrun)
- COV-005: Storage and utils coverage pass
- COV-006: Gate ratchet 12%→15%
- COV-007: Gate ratchet 15%→18%
- COV-008: API service batch 2 (bot/pipeline/webhook/space/maintenance/mcp)
- COV-009: Blocked - API controller circular import issue documented
- COV-010: Plugin runtime unit tests (+0.08%)
- COV-011: RAG and vector unit tests (+0.68%)
- COV-012: Core boot and migration unit tests
- COV-013: Provider requester logic unit tests (+0.62%)

Key additions:
- tests/utils/import_isolation.py: sys.modules isolation for circular imports
- Provider requester mock tests: proved HTTP-dependent code can be tested locally
- Vector filter utilities: 100% coverage on pure functions
- API services: fake persistence pattern for unit testing

Blocked issue COV-009 documented in langbot-test-plan/1.5/issues/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 10:12:48 +08:00

338 lines
12 KiB
Python

"""Tests for VectorDBManager provider selection logic.
Tests the initialization logic that selects the appropriate VDB backend
based on configuration, without actually creating real VDB instances.
"""
from __future__ import annotations
from unittest.mock import MagicMock
from tests.utils.import_isolation import isolated_sys_modules
class TestVectorDBManagerInitialization:
"""Tests for VectorDBManager.initialize provider selection."""
def _create_mock_app(self, vdb_config: dict | None):
"""Create mock app with vdb configuration."""
mock_app = MagicMock()
mock_app.instance_config = MagicMock()
mock_app.instance_config.data = MagicMock()
mock_app.instance_config.data.get = MagicMock(return_value=vdb_config)
mock_app.logger = MagicMock()
mock_app.logger.info = MagicMock()
mock_app.logger.warning = MagicMock()
return mock_app
def _make_vector_import_mocks(self):
"""Create mocks for VDB backends to prevent real imports."""
mocks = {}
# Mock core.app to break circular import
mocks['langbot.pkg.core.app'] = MagicMock()
# Mock all VDB backend implementations
for backend in ['chroma', 'qdrant', 'seekdb', 'milvus', 'pgvector_db']:
mocks[f'langbot.pkg.vector.vdbs.{backend}'] = MagicMock()
return mocks
def test_initialize_no_config_defaults_to_chroma(self):
"""No vdb config defaults to Chroma."""
mock_app = self._create_mock_app(None)
mocks = self._make_vector_import_mocks()
# Create mock Chroma class
mock_chroma_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.chroma'].ChromaVectorDatabase = mock_chroma_class
with isolated_sys_modules(mocks):
# Import after mocking
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
# Run initialize synchronously for test
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
# Chroma should be instantiated
mock_chroma_class.assert_called_once_with(mock_app)
mock_app.logger.warning.assert_called()
def test_initialize_chroma_backend(self):
"""Explicit chroma config uses Chroma backend."""
vdb_config = {'use': 'chroma'}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_chroma_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.chroma'].ChromaVectorDatabase = mock_chroma_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_chroma_class.assert_called_once_with(mock_app)
mock_app.logger.info.assert_called()
def test_initialize_qdrant_backend(self):
"""Qdrant config uses Qdrant backend."""
vdb_config = {'use': 'qdrant'}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_qdrant_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.qdrant'].QdrantVectorDatabase = mock_qdrant_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_qdrant_class.assert_called_once_with(mock_app)
def test_initialize_seekdb_backend(self):
"""SeekDB config uses SeekDB backend."""
vdb_config = {'use': 'seekdb'}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_seekdb_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.seekdb'].SeekDBVectorDatabase = mock_seekdb_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_seekdb_class.assert_called_once_with(mock_app)
def test_initialize_milvus_backend_with_uri(self):
"""Milvus config with custom URI."""
vdb_config = {
'use': 'milvus',
'milvus': {
'uri': 'http://localhost:19530',
'token': 'root:Milvus',
'db_name': 'langbot_db'
}
}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_milvus_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.milvus'].MilvusVectorDatabase = mock_milvus_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_milvus_class.assert_called_once_with(
mock_app,
uri='http://localhost:19530',
token='root:Milvus',
db_name='langbot_db'
)
def test_initialize_milvus_backend_defaults(self):
"""Milvus defaults when config not fully specified."""
vdb_config = {'use': 'milvus'}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_milvus_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.milvus'].MilvusVectorDatabase = mock_milvus_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
# Should use default values
mock_milvus_class.assert_called_once_with(
mock_app,
uri='./data/milvus.db',
token=None,
db_name='default'
)
def test_initialize_pgvector_with_connection_string(self):
"""pgvector with connection string."""
vdb_config = {
'use': 'pgvector',
'pgvector': {
'connection_string': 'postgresql://user:pass@host:5432/langbot'
}
}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_pgvector_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.pgvector_db'].PgVectorDatabase = mock_pgvector_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_pgvector_class.assert_called_once_with(
mock_app,
connection_string='postgresql://user:pass@host:5432/langbot'
)
def test_initialize_pgvector_with_individual_params(self):
"""pgvector with individual connection parameters."""
vdb_config = {
'use': 'pgvector',
'pgvector': {
'host': 'db.example.com',
'port': 5433,
'database': 'vectordb',
'user': 'admin',
'password': 'secret'
}
}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_pgvector_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.pgvector_db'].PgVectorDatabase = mock_pgvector_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_pgvector_class.assert_called_once_with(
mock_app,
host='db.example.com',
port=5433,
database='vectordb',
user='admin',
password='secret'
)
def test_initialize_pgvector_defaults(self):
"""pgvector defaults when no config params."""
vdb_config = {'use': 'pgvector'}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_pgvector_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.pgvector_db'].PgVectorDatabase = mock_pgvector_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_pgvector_class.assert_called_once_with(
mock_app,
host='localhost',
port=5432,
database='langbot',
user='postgres',
password='postgres'
)
def test_initialize_unknown_backend_defaults_to_chroma(self):
"""Unknown vdb type defaults to Chroma with warning."""
vdb_config = {'use': 'unknown_backend'}
mock_app = self._create_mock_app(vdb_config)
mocks = self._make_vector_import_mocks()
mock_chroma_class = MagicMock()
mocks['langbot.pkg.vector.vdbs.chroma'].ChromaVectorDatabase = mock_chroma_class
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
import asyncio
asyncio.get_event_loop().run_until_complete(mgr.initialize())
mock_chroma_class.assert_called_once_with(mock_app)
mock_app.logger.warning.assert_called()
# Should warn about no valid backend
warning_msg = mock_app.logger.warning.call_args[0][0]
assert 'No valid' in warning_msg or 'defaulting' in warning_msg
class TestVectorDBManagerProxies:
"""Tests for VectorDBManager proxy methods."""
def test_get_supported_search_types_no_vector_db(self):
"""get_supported_search_types returns vector when no vector_db."""
mock_app = MagicMock()
mock_app.instance_config = MagicMock()
mock_app.instance_config.data = MagicMock()
mock_app.instance_config.data.get = MagicMock(return_value=None)
mock_app.logger = MagicMock()
mocks = {'langbot.pkg.core.app': MagicMock()}
for backend in ['chroma', 'qdrant', 'seekdb', 'milvus', 'pgvector_db']:
mocks[f'langbot.pkg.vector.vdbs.{backend}'] = MagicMock()
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
mgr.vector_db = None # Explicitly None
result = mgr.get_supported_search_types()
assert result == ['vector']
def test_get_supported_search_types_with_vector_db(self):
"""get_supported_search_types delegates to vector_db."""
mock_app = MagicMock()
# Create mock vector_db with supported_search_types
mock_vector_db = MagicMock()
mock_vector_db.supported_search_types = MagicMock(
return_value=[
MagicMock(value='vector'),
MagicMock(value='full_text'),
]
)
mocks = {'langbot.pkg.core.app': MagicMock()}
for backend in ['chroma', 'qdrant', 'seekdb', 'milvus', 'pgvector_db']:
mocks[f'langbot.pkg.vector.vdbs.{backend}'] = MagicMock()
with isolated_sys_modules(mocks):
from langbot.pkg.vector.mgr import VectorDBManager
mgr = VectorDBManager(mock_app)
mgr.vector_db = mock_vector_db
result = mgr.get_supported_search_types()
assert result == ['vector', 'full_text']