Files
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

338 lines
12 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from unittest.mock import AsyncMock, Mock, patch
from pinecone import FetchResponse, IndexModel, Metric, QueryResponse, ServerlessSpec, Vector
from pinecone.core.openapi.db_data.models import (
Hit,
ScoredVector,
SearchRecordsResponse,
SearchRecordsResponseResult,
SearchUsage,
)
from pinecone.db_data.index_asyncio import _IndexAsyncio
from pytest import fixture, mark, raises
from semantic_kernel.connectors.pinecone import PineconeCollection, PineconeStore
from semantic_kernel.exceptions.vector_store_exceptions import VectorStoreInitializationException
BASE_PATH_ASYNCIO = "pinecone.PineconeAsyncio"
BASE_PATH_INDEX_CLIENT_ASYNCIO = "pinecone.db_data.index_asyncio._IndexAsyncio"
@fixture
def embed(request) -> dict[str, Any] | None:
if hasattr(request, "param"):
return request.param
return None
@fixture
def mock_index_model(embed: dict[str, Any] | None):
"""Mock IndexModel for testing."""
mock_index_model = Mock(spec=IndexModel)
mock_index_model.name = "test"
mock_index_model.embed = embed
mock_index_model.host = "test_host"
return mock_index_model
@fixture(autouse=True)
def mock_list_collection_names(mock_index_model):
with patch(f"{BASE_PATH_ASYNCIO}.list_indexes") as mock_list_indexes:
mock_list_indexes.return_value = [mock_index_model]
yield mock_list_indexes
@fixture(autouse=True)
def mock_create_index(mock_index_model):
with patch(f"{BASE_PATH_ASYNCIO}.create_index") as mock_create_index:
mock_create_index.return_value = mock_index_model
yield mock_create_index
@fixture(autouse=True)
def mock_create_index_for_model(mock_index_model):
with patch(f"{BASE_PATH_ASYNCIO}.create_index_for_model") as mock_create_index_for_model:
mock_create_index_for_model.return_value = mock_index_model
yield mock_create_index_for_model
@fixture(autouse=True)
def mock_describe_index(mock_index_model):
with patch(f"{BASE_PATH_ASYNCIO}.describe_index") as mock_describe_index:
mock_describe_index.return_value = mock_index_model
yield mock_describe_index
@fixture(autouse=True)
def mock_has_index():
with patch(f"{BASE_PATH_ASYNCIO}.has_index") as mock_has_index:
mock_create_index.return_value = True
yield mock_has_index
@fixture(autouse=True)
def mock_index_asyncio():
mock_index_asyncio = AsyncMock(spec=_IndexAsyncio)
mock_index_asyncio.close.return_value = None
with patch(f"{BASE_PATH_ASYNCIO}.IndexAsyncio") as mock_index:
mock_index.return_value = mock_index_asyncio
yield mock_index
@fixture(autouse=True)
def mock_delete_index():
with patch(f"{BASE_PATH_ASYNCIO}.delete_index") as mock_delete:
yield mock_delete
@fixture
async def store(pinecone_unit_test_env) -> PineconeStore:
"""Fixture to create a Pinecone store."""
async with PineconeStore() as store:
yield store
@fixture
async def collection(pinecone_unit_test_env, definition) -> PineconeCollection:
"""Fixture to create a Pinecone store."""
async with PineconeCollection(
collection_name="test_collection",
record_type=dict,
definition=definition,
) as collection:
yield collection
async def test_create_store(pinecone_unit_test_env):
"""Test the creation of a Pinecone store."""
# Create a Pinecone store
store = PineconeStore()
assert store is not None
assert store.client is not None
@mark.parametrize("exclude_list", [["PINECONE_API_KEY"]], indirect=True)
async def test_create_store_fail(pinecone_unit_test_env):
"""Test the creation of a Pinecone store."""
with raises(VectorStoreInitializationException):
PineconeStore(env_file_path="test.env")
def test_create_store_grpc(pinecone_unit_test_env):
"""Test the creation of a Pinecone store."""
# Create a Pinecone store
store = PineconeStore(use_grpc=True)
assert store is not None
assert store.client is not None
@mark.parametrize("exclude_list", [["PINECONE_API_KEY"]], indirect=True)
async def test_ensure_collection_exists_fail(pinecone_unit_test_env, definition):
with raises(VectorStoreInitializationException):
PineconeCollection(
collection_name="test_collection",
record_type=dict,
definition=definition,
env_file_path="test.env",
)
async def test_get_collection(store: PineconeStore, definition):
"""Test the creation of a Pinecone collection."""
# Create a collection
collection = store.get_collection(collection_name="test_collection", record_type=dict, definition=definition)
assert collection is not None
assert collection.collection_name == "test_collection"
async def test_list_collection_names(store: PineconeStore):
"""Test the listing of Pinecone collections."""
# List collections
collections = await store.list_collection_names()
assert collections is not None
assert len(collections) == 1
assert collections[0] == "test"
@mark.parametrize("embed", [None, {"model": "test-model"}])
async def test_load_index_client(collection, mock_index_asyncio):
# Test loading the index client
await collection._load_index_client()
assert collection.index is not None
assert collection.index_client is not None
assert isinstance(collection.index_client, _IndexAsyncio)
assert collection.embed_settings == collection.index.embed
async def test_ensure_collection_exists(collection, mock_create_index):
await collection.ensure_collection_exists()
assert collection.index is not None
assert collection.index_client is not None
mock_create_index.assert_awaited_once_with(
name=collection.collection_name,
spec=ServerlessSpec(cloud="aws", region="us-east-1"),
dimension=5,
metric=Metric.COSINE,
vector_type="dense",
)
@mark.parametrize("embed", [{"model": "test-model"}])
async def test_ensure_collection_exists_integrated(collection, mock_create_index_for_model):
await collection.ensure_collection_exists(embed={"model": "test-model"})
assert collection.index is not None
assert collection.index_client is not None
mock_create_index_for_model.assert_awaited_once_with(
name=collection.collection_name,
cloud="aws",
region="us-east-1",
embed={"model": "test-model", "metric": Metric.COSINE, "field_map": {"text": "vector"}},
)
async def test_ensure_collection_deleted(collection):
# Test deleting the collection
await collection.ensure_collection_deleted()
assert collection.index is None
assert collection.index_client is None
async def test_upsert(collection):
record = {
"id": "test_id",
"vector": [0.1, 0.2, 0.3, 0.4, 0.5],
"content": "test_content",
}
pinecone_vector = Vector(values=record["vector"], id=record["id"], metadata={"content": record["content"]})
await collection._load_index_client()
with patch.object(collection.index_client, "upsert", new_callable=AsyncMock) as mock_upsert:
await collection.upsert(record)
mock_upsert.assert_awaited_once_with(
[pinecone_vector],
namespace=collection.namespace,
)
@mark.parametrize("embed", [{"model": "test-model"}])
async def test_upsert_embed(collection):
record = {
"id": "test_id",
"content": "test_content",
"vector": [0.1, 0.2, 0.3, 0.4, 0.5],
}
await collection._load_index_client()
with patch.object(collection.index_client, "upsert_records", new_callable=AsyncMock) as mock_upsert:
await collection.upsert(record)
mock_upsert.assert_awaited_once_with(
records=[{"_id": record["id"], "content": record["content"]}],
namespace=collection.namespace,
)
async def test_get(collection):
record = {
"id": "test_id",
"vector": [0.1, 0.2, 0.3, 0.4, 0.5],
"content": "test_content",
}
fetch_response = FetchResponse(
namespace="",
vectors={
record["id"]: Vector(values=record["vector"], id=record["id"], metadata={"content": record["content"]})
},
usage={},
)
await collection._load_index_client()
with patch.object(collection.index_client, "fetch", new_callable=AsyncMock) as mock_fetch:
mock_fetch.return_value = fetch_response
get_record = await collection.get(record["id"])
mock_fetch.assert_awaited_once_with(
ids=[record["id"]],
namespace=collection.namespace,
)
assert record["id"] == get_record["id"]
assert record["content"] == get_record["content"]
async def test_delete(collection):
await collection._load_index_client()
with patch.object(collection.index_client, "delete", new_callable=AsyncMock) as mock_delete:
await collection.delete("test_id")
mock_delete.assert_awaited_once_with(
ids=["test_id"],
namespace=collection.namespace,
)
async def test_search(collection):
record = {
"id": "test_id",
"vector": [0.1, 0.2, 0.3, 0.4, 0.5],
"content": "test_content",
}
query_response = QueryResponse._from_openapi_data(
namespace="",
matches=[
ScoredVector(**{
"values": record["vector"],
"id": record["id"],
"metadata": {"content": record["content"]},
"score": 0.1,
})
],
)
await collection._load_index_client()
with patch.object(collection.index_client, "query", new_callable=AsyncMock) as mock_query:
mock_query.return_value = query_response
query_response = await collection.search(
vector=[0.1, 0.2, 0.3, 0.4, 0.5],
top=1,
include_vectors=True,
filter=lambda x: x.content == "test_content",
)
mock_query.assert_awaited_once_with(
vector=[0.1, 0.2, 0.3, 0.4, 0.5],
top_k=1,
include_metadata=True,
include_values=True,
namespace=collection.namespace,
filter={"content": "test_content"},
)
assert query_response.total_count == 1
async for result in query_response.results:
assert result.record == record
assert result.score == 0.1
@mark.parametrize("embed", [{"model": "test-model"}])
async def test_search_embed(collection):
record = {"id": "test_id", "content": "test_content", "vector": None}
query_response = SearchRecordsResponse._from_openapi_data(
result=SearchRecordsResponseResult._from_openapi_data(**{
"hits": [
Hit(**{
"_id": record["id"],
"fields": {"id": record["id"], "content": record["content"]},
"_score": 0.1,
})
]
}),
usage=SearchUsage(read_units=0),
)
await collection._load_index_client()
with patch.object(collection.index_client, "search_records", new_callable=AsyncMock) as mock_query:
mock_query.return_value = query_response
query_response = await collection.search(values="test", top=1, include_vectors=True)
mock_query.assert_awaited_once_with(
query={"inputs": {"text": "test"}, "top_k": 1},
namespace=collection.namespace,
)
assert query_response.total_count == 1
async for result in query_response.results:
assert result.record == record
assert result.score == 0.1