mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-18 19:44:21 +00:00
test: format test suite (#2252)
This commit is contained in:
@@ -68,11 +68,7 @@ class TestNormalizeFilter:
|
||||
|
||||
def test_normalize_filter_multiple_conditions(self):
|
||||
"""Multiple top-level keys are AND-ed (returned as multiple triples)."""
|
||||
result = normalize_filter({
|
||||
'file_id': 'abc',
|
||||
'status': {'$ne': 'deleted'},
|
||||
'created_at': {'$gte': 1700000000}
|
||||
})
|
||||
result = normalize_filter({'file_id': 'abc', 'status': {'$ne': 'deleted'}, 'created_at': {'$gte': 1700000000}})
|
||||
|
||||
assert len(result) == 3
|
||||
# Order should match dict iteration order
|
||||
@@ -149,11 +145,7 @@ class TestStripUnsupportedFields:
|
||||
('file_id', '$eq', 'def'),
|
||||
]
|
||||
|
||||
result = strip_unsupported_fields(
|
||||
triples,
|
||||
{'file_id', 'chunk_uuid'},
|
||||
field_aliases={'uuid': 'chunk_uuid'}
|
||||
)
|
||||
result = strip_unsupported_fields(triples, {'file_id', 'chunk_uuid'}, field_aliases={'uuid': 'chunk_uuid'})
|
||||
|
||||
assert len(result) == 2
|
||||
# 'uuid' should be resolved to 'chunk_uuid'
|
||||
@@ -169,7 +161,7 @@ class TestStripUnsupportedFields:
|
||||
result = strip_unsupported_fields(
|
||||
triples,
|
||||
{'file_id'}, # chunk_uuid not supported
|
||||
field_aliases={'uuid': 'chunk_uuid'}
|
||||
field_aliases={'uuid': 'chunk_uuid'},
|
||||
)
|
||||
|
||||
assert result == []
|
||||
@@ -207,4 +199,5 @@ class TestSupportedOpsConstant:
|
||||
def test_supported_ops_is_frozenset(self):
|
||||
"""SUPPORTED_OPS is a frozenset for immutability."""
|
||||
from collections.abc import Set
|
||||
assert isinstance(SUPPORTED_OPS, Set)
|
||||
|
||||
assert isinstance(SUPPORTED_OPS, Set)
|
||||
|
||||
@@ -55,6 +55,7 @@ class TestVectorDBManagerInitialization:
|
||||
|
||||
# Run initialize synchronously for test
|
||||
import asyncio
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(mgr.initialize())
|
||||
|
||||
# Chroma should be instantiated
|
||||
@@ -76,6 +77,7 @@ class TestVectorDBManagerInitialization:
|
||||
mgr = VectorDBManager(mock_app)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(mgr.initialize())
|
||||
|
||||
mock_chroma_class.assert_called_once_with(mock_app)
|
||||
@@ -96,6 +98,7 @@ class TestVectorDBManagerInitialization:
|
||||
mgr = VectorDBManager(mock_app)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(mgr.initialize())
|
||||
|
||||
mock_qdrant_class.assert_called_once_with(mock_app)
|
||||
@@ -115,6 +118,7 @@ class TestVectorDBManagerInitialization:
|
||||
mgr = VectorDBManager(mock_app)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(mgr.initialize())
|
||||
|
||||
mock_seekdb_class.assert_called_once_with(mock_app)
|
||||
@@ -123,11 +127,7 @@ class TestVectorDBManagerInitialization:
|
||||
"""Milvus config with custom URI."""
|
||||
vdb_config = {
|
||||
'use': 'milvus',
|
||||
'milvus': {
|
||||
'uri': 'http://localhost:19530',
|
||||
'token': 'root:Milvus',
|
||||
'db_name': 'langbot_db'
|
||||
}
|
||||
'milvus': {'uri': 'http://localhost:19530', 'token': 'root:Milvus', 'db_name': 'langbot_db'},
|
||||
}
|
||||
mock_app = self._create_mock_app(vdb_config)
|
||||
|
||||
@@ -141,13 +141,11 @@ class TestVectorDBManagerInitialization:
|
||||
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'
|
||||
mock_app, uri='http://localhost:19530', token='root:Milvus', db_name='langbot_db'
|
||||
)
|
||||
|
||||
def test_initialize_milvus_backend_defaults(self):
|
||||
@@ -165,24 +163,15 @@ class TestVectorDBManagerInitialization:
|
||||
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'
|
||||
)
|
||||
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'
|
||||
}
|
||||
}
|
||||
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()
|
||||
@@ -195,11 +184,11 @@ class TestVectorDBManagerInitialization:
|
||||
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'
|
||||
mock_app, connection_string='postgresql://user:pass@host:5432/langbot'
|
||||
)
|
||||
|
||||
def test_initialize_pgvector_with_individual_params(self):
|
||||
@@ -211,8 +200,8 @@ class TestVectorDBManagerInitialization:
|
||||
'port': 5433,
|
||||
'database': 'vectordb',
|
||||
'user': 'admin',
|
||||
'password': 'secret'
|
||||
}
|
||||
'password': 'secret',
|
||||
},
|
||||
}
|
||||
mock_app = self._create_mock_app(vdb_config)
|
||||
|
||||
@@ -226,15 +215,11 @@ class TestVectorDBManagerInitialization:
|
||||
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'
|
||||
mock_app, host='db.example.com', port=5433, database='vectordb', user='admin', password='secret'
|
||||
)
|
||||
|
||||
def test_initialize_pgvector_defaults(self):
|
||||
@@ -252,15 +237,11 @@ class TestVectorDBManagerInitialization:
|
||||
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'
|
||||
mock_app, host='localhost', port=5432, database='langbot', user='postgres', password='postgres'
|
||||
)
|
||||
|
||||
def test_initialize_unknown_backend_defaults_to_chroma(self):
|
||||
@@ -278,6 +259,7 @@ class TestVectorDBManagerInitialization:
|
||||
mgr = VectorDBManager(mock_app)
|
||||
|
||||
import asyncio
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(mgr.initialize())
|
||||
|
||||
mock_chroma_class.assert_called_once_with(mock_app)
|
||||
@@ -335,4 +317,4 @@ class TestVectorDBManagerProxies:
|
||||
mgr.vector_db = mock_vector_db
|
||||
|
||||
result = mgr.get_supported_search_types()
|
||||
assert result == ['vector', 'full_text']
|
||||
assert result == ['vector', 'full_text']
|
||||
|
||||
@@ -39,6 +39,7 @@ class TestVectorDatabaseAbstractMethods:
|
||||
|
||||
def test_abstract_methods_required(self):
|
||||
"""Subclass must implement all abstract methods."""
|
||||
|
||||
class IncompleteVectorDB(VectorDatabase):
|
||||
pass
|
||||
|
||||
@@ -47,11 +48,21 @@ class TestVectorDatabaseAbstractMethods:
|
||||
|
||||
def test_supported_search_types_default(self):
|
||||
"""Default supported_search_types returns [VECTOR]."""
|
||||
|
||||
class MinimalVectorDB(VectorDatabase):
|
||||
async def add_embeddings(self, collection, ids, embeddings_list, metadatas, documents=None):
|
||||
pass
|
||||
|
||||
async def search(self, collection, query_embedding, k=5, search_type='vector', query_text='', filter=None, vector_weight=None):
|
||||
async def search(
|
||||
self,
|
||||
collection,
|
||||
query_embedding,
|
||||
k=5,
|
||||
search_type='vector',
|
||||
query_text='',
|
||||
filter=None,
|
||||
vector_weight=None,
|
||||
):
|
||||
pass
|
||||
|
||||
async def delete_by_file_id(self, collection, file_id):
|
||||
@@ -71,11 +82,21 @@ class TestVectorDatabaseAbstractMethods:
|
||||
|
||||
def test_list_by_filter_default_implementation(self):
|
||||
"""list_by_filter has default implementation returning empty."""
|
||||
|
||||
class MinimalVectorDB(VectorDatabase):
|
||||
async def add_embeddings(self, collection, ids, embeddings_list, metadatas, documents=None):
|
||||
pass
|
||||
|
||||
async def search(self, collection, query_embedding, k=5, search_type='vector', query_text='', filter=None, vector_weight=None):
|
||||
async def search(
|
||||
self,
|
||||
collection,
|
||||
query_embedding,
|
||||
k=5,
|
||||
search_type='vector',
|
||||
query_text='',
|
||||
filter=None,
|
||||
vector_weight=None,
|
||||
):
|
||||
pass
|
||||
|
||||
async def delete_by_file_id(self, collection, file_id):
|
||||
@@ -93,9 +114,8 @@ class TestVectorDatabaseAbstractMethods:
|
||||
db = MinimalVectorDB()
|
||||
# list_by_filter should return empty list and -1 for total
|
||||
import asyncio
|
||||
result = asyncio.get_event_loop().run_until_complete(
|
||||
db.list_by_filter('test_collection')
|
||||
)
|
||||
|
||||
result = asyncio.get_event_loop().run_until_complete(db.list_by_filter('test_collection'))
|
||||
assert result == ([], -1)
|
||||
|
||||
|
||||
@@ -105,14 +125,17 @@ class TestVectorDatabaseInterface:
|
||||
@pytest.fixture
|
||||
def mock_vector_db(self):
|
||||
"""Create a minimal mock VectorDatabase for testing."""
|
||||
|
||||
class MockVectorDB(VectorDatabase):
|
||||
def __init__(self):
|
||||
self.add_embeddings = AsyncMock()
|
||||
self.search = AsyncMock(return_value={
|
||||
'ids': [['id1', 'id2']],
|
||||
'distances': [[0.1, 0.2]],
|
||||
'metadatas': [[{'key': 'val1'}, {'key': 'val2'}]]
|
||||
})
|
||||
self.search = AsyncMock(
|
||||
return_value={
|
||||
'ids': [['id1', 'id2']],
|
||||
'distances': [[0.1, 0.2]],
|
||||
'metadatas': [[{'key': 'val1'}, {'key': 'val2'}]],
|
||||
}
|
||||
)
|
||||
self.delete_by_file_id = AsyncMock()
|
||||
self.delete_by_filter = AsyncMock(return_value=5)
|
||||
self.get_or_create_collection = AsyncMock()
|
||||
@@ -121,7 +144,16 @@ class TestVectorDatabaseInterface:
|
||||
async def add_embeddings(self, collection, ids, embeddings_list, metadatas, documents=None):
|
||||
pass
|
||||
|
||||
async def search(self, collection, query_embedding, k=5, search_type='vector', query_text='', filter=None, vector_weight=None):
|
||||
async def search(
|
||||
self,
|
||||
collection,
|
||||
query_embedding,
|
||||
k=5,
|
||||
search_type='vector',
|
||||
query_text='',
|
||||
filter=None,
|
||||
vector_weight=None,
|
||||
):
|
||||
pass
|
||||
|
||||
async def delete_by_file_id(self, collection, file_id):
|
||||
@@ -146,7 +178,7 @@ class TestVectorDatabaseInterface:
|
||||
ids=['id1', 'id2'],
|
||||
embeddings_list=[[0.1, 0.2], [0.3, 0.4]],
|
||||
metadatas=[{'a': 1}, {'b': 2}],
|
||||
documents=['doc1', 'doc2']
|
||||
documents=['doc1', 'doc2'],
|
||||
)
|
||||
mock_vector_db.add_embeddings.assert_called_once()
|
||||
|
||||
@@ -162,7 +194,7 @@ class TestVectorDatabaseInterface:
|
||||
search_type='hybrid',
|
||||
query_text='search text',
|
||||
filter={'file_id': 'abc'},
|
||||
vector_weight=0.7
|
||||
vector_weight=0.7,
|
||||
)
|
||||
mock_vector_db.search.assert_called_once()
|
||||
|
||||
@@ -170,4 +202,4 @@ class TestVectorDatabaseInterface:
|
||||
async def test_delete_by_filter_returns_int(self, mock_vector_db):
|
||||
"""delete_by_filter returns int count."""
|
||||
result = await mock_vector_db.delete_by_filter('test', {'file_id': 'abc'})
|
||||
assert isinstance(result, int)
|
||||
assert isinstance(result, int)
|
||||
|
||||
@@ -5,6 +5,7 @@ Tests cover:
|
||||
- _build_milvus_expr: Milvus boolean expression string conversion
|
||||
- _build_pg_conditions: PostgreSQL SQLAlchemy conditions conversion
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib import import_module
|
||||
@@ -122,11 +123,13 @@ class TestQdrantFilterConversion:
|
||||
"""Multiple conditions are combined in must/must_not."""
|
||||
qdrant_module = get_qdrant_module()
|
||||
|
||||
result = qdrant_module._build_qdrant_filter({
|
||||
'file_id': 'abc',
|
||||
'status': {'$ne': 'deleted'},
|
||||
'created_at': {'$gte': 100},
|
||||
})
|
||||
result = qdrant_module._build_qdrant_filter(
|
||||
{
|
||||
'file_id': 'abc',
|
||||
'status': {'$ne': 'deleted'},
|
||||
'created_at': {'$gte': 100},
|
||||
}
|
||||
)
|
||||
|
||||
assert len(result.must) == 2 # file_id eq + created_at gte
|
||||
assert len(result.must_not) == 1 # status ne
|
||||
@@ -198,10 +201,12 @@ class TestMilvusFilterConversion:
|
||||
"""Multiple conditions are joined with 'and'."""
|
||||
milvus_module = get_milvus_module()
|
||||
|
||||
result = milvus_module._build_milvus_expr({
|
||||
'file_id': 'abc',
|
||||
'chunk_uuid': {'$ne': 'def'},
|
||||
})
|
||||
result = milvus_module._build_milvus_expr(
|
||||
{
|
||||
'file_id': 'abc',
|
||||
'chunk_uuid': {'$ne': 'def'},
|
||||
}
|
||||
)
|
||||
assert 'and' in result
|
||||
assert 'file_id == "abc"' in result
|
||||
assert 'chunk_uuid != "def"' in result
|
||||
@@ -272,6 +277,7 @@ class TestPgVectorFilterConversion:
|
||||
assert len(result) == 1
|
||||
# Verify it's a SQLAlchemy BinaryExpression
|
||||
from sqlalchemy.sql.expression import BinaryExpression
|
||||
|
||||
assert isinstance(result[0], BinaryExpression)
|
||||
|
||||
def test_ne_operator_creates_inequality_condition(self):
|
||||
@@ -321,10 +327,12 @@ class TestPgVectorFilterConversion:
|
||||
"""Multiple conditions return list of conditions."""
|
||||
pgvector_module = get_pgvector_module()
|
||||
|
||||
result = pgvector_module._build_pg_conditions({
|
||||
'file_id': 'abc',
|
||||
'chunk_uuid': {'$ne': 'def'},
|
||||
})
|
||||
result = pgvector_module._build_pg_conditions(
|
||||
{
|
||||
'file_id': 'abc',
|
||||
'chunk_uuid': {'$ne': 'def'},
|
||||
}
|
||||
)
|
||||
|
||||
assert len(result) == 2
|
||||
|
||||
@@ -349,11 +357,13 @@ class TestPgVectorFilterConversion:
|
||||
"""Only supported fields (text, file_id, chunk_uuid) are kept."""
|
||||
pgvector_module = get_pgvector_module()
|
||||
|
||||
result = pgvector_module._build_pg_conditions({
|
||||
'text': {'$ne': ''},
|
||||
'file_id': 'abc',
|
||||
'chunk_uuid': {'$in': ['x', 'y']},
|
||||
'unsupported': 'value',
|
||||
})
|
||||
result = pgvector_module._build_pg_conditions(
|
||||
{
|
||||
'text': {'$ne': ''},
|
||||
'file_id': 'abc',
|
||||
'chunk_uuid': {'$in': ['x', 'y']},
|
||||
'unsupported': 'value',
|
||||
}
|
||||
)
|
||||
|
||||
assert len(result) == 3 # Only supported fields
|
||||
assert len(result) == 3 # Only supported fields
|
||||
|
||||
Reference in New Issue
Block a user