Files
semantica/tests/vector_store/test_milvus_get_collection.py
T
Kevin 8e7aaee4f5 fix(vector-store): validate collection schema in MilvusStore.get_collection (#1344)
`get_collection()` attached any collection right after the existence check, with no look at its schema. A collection with an INT64 primary key, or one missing the `metadata` field entirely, would attach without complaint and only fail later, inside `get_vector()` or `get_metadata()`, with an error that gave no hint the real problem was upstream at attach time.

This adds a schema check between the attach and the assignment to `self.collection`, so a mismatch is caught at the point of failure instead of surfacing three calls later as an unrelated-looking error. The check validates against exactly the shape `create_collection()` builds: a `VARCHAR` primary key named `id` with `auto_id=False`, a `FLOAT_VECTOR` field named `vector`, and a `JSON` field named `metadata`. Anything else, wrong dtype, wrong name, a missing field, or an auto-generated id, is rejected before the store ever holds a reference to it.

The auto_id and metadata-dtype checks were added in a second pass after review. A collection with `auto_id=True` still attached cleanly and only broke once the store tried to insert with the explicit ids it always sends, and a `metadata` field that existed but wasn't `JSON`-typed only broke during a later write or metadata filter, for the same reason: schema drift that looked fine at attach time and failed downstream instead of at the source.

Nine tests cover this: the one matching-schema case that should succeed, and each rejection path independently, wrong pk dtype, missing pk, wrong pk name, auto_id pk, missing vector field, wrong vector dtype, missing metadata field, and non-JSON metadata.

Closes #1331.
2026-09-02 00:58:26 +05:00

142 lines
4.8 KiB
Python

"""Tests for MilvusStore.get_collection schema validation (#1331)."""
from unittest import TestCase
from unittest.mock import MagicMock, patch
from semantica.vector_store.milvus_store import MilvusStore
from semantica.utils.exceptions import ProcessingError
def _field(name, dtype_name, primary=False, auto_id=False):
f = MagicMock()
f.name = name
f.is_primary = primary
f.auto_id = auto_id
f.dtype.name = dtype_name
return f
class MilvusGetCollectionSchemaTest(TestCase):
def setUp(self):
self.patches = [
patch("semantica.vector_store.milvus_store.MILVUS_AVAILABLE", True),
patch("semantica.vector_store.milvus_store.utility"),
patch("semantica.vector_store.milvus_store.Collection"),
]
for p in self.patches:
p.start()
# utility.has_collection() must return truthy
import semantica.vector_store.milvus_store as m
m.utility.has_collection.return_value = True
def tearDown(self):
for p in reversed(self.patches):
p.stop()
def _make_store(self, coll):
store = MilvusStore()
store.client = MagicMock() # skip real connect
import semantica.vector_store.milvus_store as m
m.Collection.return_value = coll
return store
def _assert_rejected(self, store, expected_msg):
with self.assertRaises(ProcessingError) as ctx:
store.get_collection("c")
self.assertIn(expected_msg, str(ctx.exception))
self.assertIsNone(store.collection)
def test_accepts_matching_schema(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "VARCHAR", primary=True),
_field("vector", "FLOAT_VECTOR"),
_field("metadata", "JSON"),
]
store = self._make_store(coll)
result = store.get_collection("c")
self.assertIsNotNone(result)
self.assertIsNotNone(store.collection)
self.assertIsNotNone(store.search_engine)
self.assertEqual(store.collection.collection_name, "c")
def test_rejects_non_varchar_primary_key(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "INT64", primary=True),
_field("vector", "FLOAT_VECTOR"),
_field("metadata", "JSON"),
]
store = self._make_store(coll)
self._assert_rejected(store, "has an invalid primary key")
def test_rejects_missing_primary_key(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "VARCHAR"),
_field("vector", "FLOAT_VECTOR"),
_field("metadata", "JSON"),
]
store = self._make_store(coll)
self._assert_rejected(store, "has an invalid primary key")
def test_rejects_wrongly_named_primary_key(self):
coll = MagicMock()
coll.schema.fields = [
_field("pk", "VARCHAR", primary=True),
_field("vector", "FLOAT_VECTOR"),
_field("metadata", "JSON"),
]
store = self._make_store(coll)
self._assert_rejected(store, "has an invalid primary key")
def test_rejects_missing_metadata_field(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "VARCHAR", primary=True),
_field("vector", "FLOAT_VECTOR"),
]
store = self._make_store(coll)
self._assert_rejected(store, "is missing required field 'metadata'")
def test_rejects_missing_vector_field(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "VARCHAR", primary=True),
_field("metadata", "JSON"),
]
store = self._make_store(coll)
self._assert_rejected(store, "is missing required field 'vector'")
def test_rejects_wrong_vector_dtype(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "VARCHAR", primary=True),
_field("vector", "BINARY_VECTOR"),
_field("metadata", "JSON"),
]
store = self._make_store(coll)
self._assert_rejected(store, "has an invalid vector field")
def test_rejects_auto_id_primary_key(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "VARCHAR", primary=True, auto_id=True),
_field("vector", "FLOAT_VECTOR"),
_field("metadata", "JSON"),
]
store = self._make_store(coll)
self._assert_rejected(store, "has an invalid primary key")
def test_rejects_non_json_metadata(self):
coll = MagicMock()
coll.schema.fields = [
_field("id", "VARCHAR", primary=True),
_field("vector", "FLOAT_VECTOR"),
_field("metadata", "STRING"),
]
store = self._make_store(coll)
self._assert_rejected(store, "has an invalid metadata field")