import unittest from semantic_index.models import IndexDocument from semantic_index.qdrant_store import QdrantStore class FakeMatchValue: def __init__(self, value): self.value = value class FakeFieldCondition: def __init__(self, key, match=None, range=None): self.key = key self.match = match self.range = range class FakeFilter: def __init__(self, must): self.must = must class FakeFilterSelector: def __init__(self, filter): self.filter = filter class FakePointIdsList: def __init__(self, points): self.points = points class FakeQModels: MatchValue = FakeMatchValue FieldCondition = FakeFieldCondition Filter = FakeFilter FilterSelector = FakeFilterSelector PointIdsList = FakePointIdsList class PointStruct: def __init__(self, id, vector, payload): self.id = id self.vector = vector self.payload = payload class FakeCountResult: count = 7 class FakeRecord: def __init__(self): self.id = "point-id" self.payload = { "document_id": "redmine:issue:1:chunk:0", "text": "Indexed text", "source": "redmine", "project_identifier": "customer-service", } class FakeClient: def __init__(self): self.count_filter = None self.scroll_filter = None self.delete_filter = None self.delete_selector = None self.upsert_batches = [] def get_collections(self): collection = type("Collection", (), {"name": "semantic"})() return type("Collections", (), {"collections": [collection]})() def count(self, collection_name, count_filter, exact): self.count_filter = count_filter return FakeCountResult() def scroll(self, collection_name, scroll_filter, limit, with_payload, with_vectors, offset=None): self.scroll_filter = scroll_filter return [FakeRecord()], None def delete(self, collection_name, points_selector): self.delete_selector = points_selector self.delete_filter = getattr(points_selector, "filter", None) def upsert(self, collection_name, points): self.upsert_batches.append(points) class QdrantStoreReadTest(unittest.TestCase): def make_store(self): store = object.__new__(QdrantStore) store.client = FakeClient() store.collection = "semantic" store.vector_size = 1536 store.qmodels = FakeQModels store.upsert_batch_size = 2 return store def test_count_documents_builds_metadata_filter(self): store = self.make_store() count = store.count_documents(source="redmine", project_identifier="customer-service", doc_type="issue") self.assertEqual(7, count) conditions = store.client.count_filter.must self.assertEqual(["source", "project_identifier", "doc_type"], [condition.key for condition in conditions]) self.assertEqual("customer-service", conditions[1].match.value) def test_list_documents_strips_internal_payload_fields(self): store = self.make_store() documents = store.list_documents(limit=5, source="redmine", project_identifier="customer-service") self.assertEqual("redmine:issue:1:chunk:0", documents[0]["id"]) self.assertEqual("Indexed text", documents[0]["text"]) self.assertNotIn("document_id", documents[0]["payload"]) self.assertNotIn("text", documents[0]["payload"]) def test_delete_by_source_can_be_limited_to_project_scope(self): store = self.make_store() store.delete_by_source("redmine", project_identifier="customer-service") conditions = store.client.delete_filter.must self.assertEqual(["source", "project_identifier"], [condition.key for condition in conditions]) self.assertEqual("redmine", conditions[0].match.value) self.assertEqual("customer-service", conditions[1].match.value) def test_list_documents_can_be_limited_to_issue_scope(self): store = self.make_store() store.list_documents(limit=5, source="redmine", project_identifier="customer-service", issue_id=39779) conditions = store.client.scroll_filter.must self.assertEqual(["source", "project_identifier", "issue_id"], [condition.key for condition in conditions]) self.assertEqual(39779, conditions[2].match.value) def test_delete_documents_deletes_stable_document_point_ids(self): store = self.make_store() store.delete_documents(["redmine:issue:39779:chunk:0"]) self.assertEqual(1, len(store.client.delete_selector.points)) self.assertNotEqual("redmine:issue:39779:chunk:0", store.client.delete_selector.points[0]) def test_upsert_sends_points_in_batches(self): store = self.make_store() documents = [ IndexDocument(id=f"redmine:issue:{issue_id}:chunk:0", text=f"Issue {issue_id}", payload={"source": "redmine"}) for issue_id in range(5) ] vectors = [[0.1, 0.2, 0.3] for _ in documents] store.upsert(documents, vectors) self.assertEqual([2, 2, 1], [len(batch) for batch in store.client.upsert_batches]) self.assertEqual("Issue 0", store.client.upsert_batches[0][0].payload["text"]) def test_list_documents_paginates_qdrant_scroll_until_requested_limit(self): class PagedClient(FakeClient): def __init__(self): super().__init__() self.offsets = [] def scroll(self, collection_name, scroll_filter, limit, with_payload, with_vectors, offset=None): self.offsets.append(offset) first = FakeRecord() first.payload = {**first.payload, "document_id": f"doc:{len(self.offsets)}a"} second = FakeRecord() second.payload = {**second.payload, "document_id": f"doc:{len(self.offsets)}b"} if offset is None: return [first, second], "next" return [first, second], None store = self.make_store() store.client = PagedClient() documents = store.list_documents(limit=3, source="redmine") self.assertEqual(["doc:1a", "doc:1b", "doc:2a"], [document["id"] for document in documents]) self.assertEqual([None, "next"], store.client.offsets) if __name__ == "__main__": unittest.main()