chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,6 @@
# Suppressing errors for Test projects under dotnet folder
[*.cs]
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations
@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Microsoft.SemanticKernel.Connectors.Qdrant.UnitTests</AssemblyName>
<RootNamespace>Microsoft.SemanticKernel.Connectors.Qdrant.UnitTests</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);CA2007,CA1806,CA1869,CA1861,IDE0300,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0050</NoWarn>
<NoWarn>$(NoWarn);MEVD9001</NoWarn> <!-- Experimental MEVD connector-facing APIs -->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="Moq"/>
<PackageReference Include="xunit"/>
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/AssertExtensions.cs"
Link="%(RecursiveDir)%(Filename)%(Extension)"/>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/HttpMessageHandlerStub.cs"
Link="%(RecursiveDir)%(Filename)%(Extension)"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\VectorData\Qdrant\Qdrant.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,84 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Extensions.VectorData;
using Microsoft.Extensions.VectorData.ProviderServices;
using Qdrant.Client.Grpc;
using Xunit;
namespace Microsoft.SemanticKernel.Connectors.Qdrant.UnitTests;
/// <summary>
/// Contains tests for the <see cref="QdrantCollectionCreateMapping"/> class.
/// </summary>
public class QdrantCollectionCreateMappingTests
{
[Fact]
public void MapSingleVectorCreatesVectorParams()
{
// Arrange.
var vectorProperty = new VectorPropertyModel("testvector", typeof(ReadOnlyMemory<float>)) { Dimensions = 4, DistanceFunction = DistanceFunction.DotProductSimilarity };
// Act.
var actual = QdrantCollectionCreateMapping.MapSingleVector(vectorProperty);
// Assert.
Assert.NotNull(actual);
Assert.Equal(Distance.Dot, actual.Distance);
Assert.Equal(4ul, actual.Size);
}
[Fact]
public void MapSingleVectorDefaultsToCosine()
{
// Arrange.
var vectorProperty = new VectorPropertyModel("testvector", typeof(ReadOnlyMemory<float>)) { Dimensions = 4 };
// Act.
var actual = QdrantCollectionCreateMapping.MapSingleVector(vectorProperty);
// Assert.
Assert.Equal(Distance.Cosine, actual.Distance);
}
[Fact]
public void MapSingleVectorThrowsForUnsupportedDistanceFunction()
{
// Arrange.
var vectorProperty = new VectorPropertyModel("testvector", typeof(ReadOnlyMemory<float>)) { Dimensions = 4, DistanceFunction = DistanceFunction.CosineDistance };
// Act and assert.
Assert.Throws<NotSupportedException>(() => QdrantCollectionCreateMapping.MapSingleVector(vectorProperty));
}
[Fact]
public void MapNamedVectorsCreatesVectorParamsMap()
{
// Arrange.
var vectorProperties = new VectorPropertyModel[]
{
new("testvector1", typeof(ReadOnlyMemory<float>))
{
Dimensions = 10,
DistanceFunction = DistanceFunction.EuclideanDistance,
StorageName = "storage_testvector1"
},
new("testvector2", typeof(ReadOnlyMemory<float>))
{
Dimensions = 20,
StorageName = "storage_testvector2"
}
};
// Act.
var actual = QdrantCollectionCreateMapping.MapNamedVectors(vectorProperties);
// Assert.
Assert.NotNull(actual);
Assert.Equal(2, actual.Map.Count);
Assert.Equal(10ul, actual.Map["storage_testvector1"].Size);
Assert.Equal(Distance.Euclid, actual.Map["storage_testvector1"].Distance);
Assert.Equal(20ul, actual.Map["storage_testvector2"].Size);
Assert.Equal(Distance.Cosine, actual.Map["storage_testvector2"].Distance);
}
}
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Extensions.VectorData;
using Qdrant.Client.Grpc;
using Xunit;
namespace Microsoft.SemanticKernel.Connectors.Qdrant.UnitTests;
/// <summary>
/// Contains tests for the <see cref="QdrantCollectionSearchMapping"/> class.
/// </summary>
public class QdrantCollectionSearchMappingTests
{
[Fact]
public void MapScoredPointToVectorSearchResultMapsResults()
{
var responseVector = VectorOutput.Parser.ParseJson("{ \"data\": [1, 2, 3] }");
// Arrange.
var scoredPoint = new ScoredPoint
{
Id = 1,
Payload = { ["storage_DataField"] = "data 1" },
Vectors = new VectorsOutput() { Vector = responseVector },
Score = 0.5f
};
var model = new QdrantModelBuilder(hasNamedVectors: false)
.Build(
typeof(DataModel),
typeof(ulong),
new()
{
Properties =
[
new VectorStoreKeyProperty("Id", typeof(ulong)),
new VectorStoreDataProperty("DataField", typeof(string)) { StorageName = "storage_DataField" },
new VectorStoreVectorProperty("Embedding", typeof(ReadOnlyMemory<float>), 10),
]
},
defaultEmbeddingGenerator: null);
var mapper = new QdrantMapper<DataModel>(model, hasNamedVectors: false);
// Act.
var actual = QdrantCollectionSearchMapping.MapScoredPointToVectorSearchResult<DataModel>(scoredPoint, mapper, true, "Qdrant", "myvectorstore", "mycollection", "query");
// Assert.
Assert.Equal(1ul, actual.Record.Id);
Assert.Equal("data 1", actual.Record.DataField);
Assert.Equal(new float[] { 1, 2, 3 }, actual.Record.Embedding.ToArray());
Assert.Equal(0.5f, actual.Score);
}
public class DataModel
{
public ulong Id { get; init; }
public string? DataField { get; init; }
public ReadOnlyMemory<float> Embedding { get; init; }
}
}
@@ -0,0 +1,781 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.VectorData;
using Moq;
using Qdrant.Client.Grpc;
using Xunit;
namespace Microsoft.SemanticKernel.Connectors.Qdrant.UnitTests;
/// <summary>
/// Contains tests for the <see cref="QdrantCollection{TKey, TRecord}"/> class.
/// </summary>
public class QdrantCollectionTests
{
private const string TestCollectionName = "testcollection";
private const ulong UlongTestRecordKey1 = 1;
private const ulong UlongTestRecordKey2 = 2;
private static readonly Guid s_guidTestRecordKey1 = Guid.Parse("11111111-1111-1111-1111-111111111111");
private static readonly Guid s_guidTestRecordKey2 = Guid.Parse("22222222-2222-2222-2222-222222222222");
private readonly Mock<MockableQdrantClient> _qdrantClientMock;
private readonly CancellationToken _testCancellationToken = new(false);
public QdrantCollectionTests()
{
this._qdrantClientMock = new Mock<MockableQdrantClient>(MockBehavior.Strict);
}
[Theory]
[InlineData(TestCollectionName, true)]
[InlineData("nonexistentcollection", false)]
public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists)
{
// Arrange.
using var sut = new QdrantCollection<ulong, SinglePropsModel<ulong>>(() => this._qdrantClientMock.Object, collectionName);
this._qdrantClientMock
.Setup(x => x.CollectionExistsAsync(
It.IsAny<string>(),
this._testCancellationToken))
.ReturnsAsync(expectedExists);
// Act.
var actual = await sut.CollectionExistsAsync(this._testCancellationToken);
// Assert.
Assert.Equal(expectedExists, actual);
}
[Fact]
public async Task CanCreateCollectionAsync()
{
// Arrange.
using var sut = new QdrantCollection<ulong, SinglePropsModel<ulong>>(() => this._qdrantClientMock.Object, TestCollectionName);
this._qdrantClientMock
.Setup(x => x.CollectionExistsAsync(
It.IsAny<string>(),
this._testCancellationToken))
.ReturnsAsync(false);
this._qdrantClientMock
.Setup(x => x.CreateCollectionAsync(
It.IsAny<string>(),
It.IsAny<VectorParams>(),
this._testCancellationToken))
.Returns(Task.CompletedTask);
this._qdrantClientMock
.Setup(x => x.CreatePayloadIndexAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<PayloadSchemaType>(),
this._testCancellationToken))
.ReturnsAsync(new UpdateResult());
// Act.
await sut.EnsureCollectionExistsAsync(this._testCancellationToken);
// Assert.
this._qdrantClientMock
.Verify(
x => x.CreateCollectionAsync(
TestCollectionName,
It.Is<VectorParams>(x => x.Size == 4),
this._testCancellationToken),
Times.Once);
this._qdrantClientMock
.Verify(
x => x.CreatePayloadIndexAsync(
TestCollectionName,
"OriginalNameData",
PayloadSchemaType.Keyword,
this._testCancellationToken),
Times.Once);
this._qdrantClientMock
.Verify(
x => x.CreatePayloadIndexAsync(
TestCollectionName,
"OriginalNameData",
PayloadSchemaType.Text,
this._testCancellationToken),
Times.Once);
this._qdrantClientMock
.Verify(
x => x.CreatePayloadIndexAsync(
TestCollectionName,
"data_storage_name",
PayloadSchemaType.Keyword,
this._testCancellationToken),
Times.Once);
}
[Fact]
public async Task CanDeleteCollectionAsync()
{
// Arrange.
using var sut = new QdrantCollection<ulong, SinglePropsModel<ulong>>(() => this._qdrantClientMock.Object, TestCollectionName);
this._qdrantClientMock
.Setup(x => x.DeleteCollectionAsync(
It.IsAny<string>(),
null,
this._testCancellationToken))
.Returns(Task.CompletedTask);
// Act.
await sut.EnsureCollectionDeletedAsync(this._testCancellationToken);
// Assert.
this._qdrantClientMock
.Verify(
x => x.DeleteCollectionAsync(
TestCollectionName,
null,
this._testCancellationToken),
Times.Once);
}
[Theory]
[MemberData(nameof(TestOptions))]
public async Task CanGetRecordWithVectorsAsync<TKey>(bool useDefinition, bool hasNamedVectors, TKey testRecordKey)
where TKey : notnull
{
using var sut = this.CreateRecordCollection<TKey>(useDefinition, hasNamedVectors);
// Arrange.
var retrievedPoint = CreateRetrievedPoint(hasNamedVectors, testRecordKey);
this.SetupRetrieveMock([retrievedPoint]);
// Act.
var actual = await sut.GetAsync(
testRecordKey,
new() { IncludeVectors = true },
this._testCancellationToken);
// Assert.
this._qdrantClientMock
.Verify(
x => x.RetrieveAsync(
TestCollectionName,
It.Is<IReadOnlyList<PointId>>(x => x.Count == 1 && (testRecordKey!.GetType() == typeof(ulong) && x[0].Num == (testRecordKey as ulong?) || testRecordKey!.GetType() == typeof(Guid) && x[0].Uuid == (testRecordKey as Guid?).ToString())),
true,
true,
null,
null,
this._testCancellationToken),
Times.Once);
Assert.NotNull(actual);
Assert.Equal(testRecordKey, actual.Key);
Assert.Equal("data 1", actual.OriginalNameData);
Assert.Equal("data 1", actual.Data);
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vector!.Value.ToArray());
}
[Theory]
[MemberData(nameof(TestOptions))]
public async Task CanGetRecordWithoutVectorsAsync<TKey>(bool useDefinition, bool hasNamedVectors, TKey testRecordKey)
where TKey : notnull
{
// Arrange.
using var sut = this.CreateRecordCollection<TKey>(useDefinition, hasNamedVectors);
var retrievedPoint = CreateRetrievedPoint(hasNamedVectors, testRecordKey);
this.SetupRetrieveMock([retrievedPoint]);
// Act.
var actual = await sut.GetAsync(
testRecordKey,
new() { IncludeVectors = false },
this._testCancellationToken);
// Assert.
this._qdrantClientMock
.Verify(
x => x.RetrieveAsync(
TestCollectionName,
It.Is<IReadOnlyList<PointId>>(x => x.Count == 1 && (testRecordKey!.GetType() == typeof(ulong) && x[0].Num == (testRecordKey as ulong?) || testRecordKey!.GetType() == typeof(Guid) && x[0].Uuid == (testRecordKey as Guid?).ToString())),
true,
false,
null,
null,
this._testCancellationToken),
Times.Once);
Assert.NotNull(actual);
Assert.Equal(testRecordKey, actual.Key);
Assert.Equal("data 1", actual.OriginalNameData);
Assert.Equal("data 1", actual.Data);
Assert.Null(actual.Vector);
}
[Theory]
[MemberData(nameof(MultiRecordTestOptions))]
public async Task CanGetManyRecordsWithVectorsAsync<TKey>(bool useDefinition, bool hasNamedVectors, TKey[] testRecordKeys)
where TKey : notnull
{
// Arrange.
using var sut = this.CreateRecordCollection<TKey>(useDefinition, hasNamedVectors);
var retrievedPoint1 = CreateRetrievedPoint(hasNamedVectors, UlongTestRecordKey1);
var retrievedPoint2 = CreateRetrievedPoint(hasNamedVectors, UlongTestRecordKey2);
this.SetupRetrieveMock(testRecordKeys.Select(x => CreateRetrievedPoint(hasNamedVectors, x)).ToList());
// Act.
var actual = await sut.GetAsync(
testRecordKeys,
new() { IncludeVectors = true },
this._testCancellationToken).ToListAsync();
// Assert.
this._qdrantClientMock
.Verify(
x => x.RetrieveAsync(
TestCollectionName,
It.Is<IReadOnlyList<PointId>>(x =>
x.Count == 2 &&
(testRecordKeys[0]!.GetType() == typeof(ulong) && x[0].Num == (testRecordKeys[0] as ulong?) || testRecordKeys[0]!.GetType() == typeof(Guid) && x[0].Uuid == (testRecordKeys[0] as Guid?).ToString()) &&
(testRecordKeys[1]!.GetType() == typeof(ulong) && x[1].Num == (testRecordKeys[1] as ulong?) || testRecordKeys[1]!.GetType() == typeof(Guid) && x[1].Uuid == (testRecordKeys[1] as Guid?).ToString())),
true,
true,
null,
null,
this._testCancellationToken),
Times.Once);
Assert.NotNull(actual);
Assert.Equal(2, actual.Count);
Assert.Equal(testRecordKeys[0], actual[0].Key);
Assert.Equal(testRecordKeys[1], actual[1].Key);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task CanDeleteUlongRecordAsync(bool useDefinition, bool hasNamedVectors)
{
// Arrange
using var sut = this.CreateRecordCollection<ulong>(useDefinition, hasNamedVectors);
this.SetupDeleteMocks();
// Act
await sut.DeleteAsync(
UlongTestRecordKey1,
cancellationToken: this._testCancellationToken);
// Assert
this._qdrantClientMock
.Verify(
x => x.DeleteAsync(
TestCollectionName,
It.Is<ulong>(x => x == UlongTestRecordKey1),
true,
null,
null,
this._testCancellationToken),
Times.Once);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task CanDeleteGuidRecordAsync(bool useDefinition, bool hasNamedVectors)
{
// Arrange
using var sut = this.CreateRecordCollection<Guid>(useDefinition, hasNamedVectors);
this.SetupDeleteMocks();
// Act
await sut.DeleteAsync(
s_guidTestRecordKey1,
cancellationToken: this._testCancellationToken);
// Assert
this._qdrantClientMock
.Verify(
x => x.DeleteAsync(
TestCollectionName,
It.Is<Guid>(x => x == s_guidTestRecordKey1),
true,
null,
null,
this._testCancellationToken),
Times.Once);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task CanDeleteManyUlongRecordsAsync(bool useDefinition, bool hasNamedVectors)
{
// Arrange
using var sut = this.CreateRecordCollection<ulong>(useDefinition, hasNamedVectors);
this.SetupDeleteMocks();
// Act
await sut.DeleteAsync(
[UlongTestRecordKey1, UlongTestRecordKey2],
cancellationToken: this._testCancellationToken);
// Assert
this._qdrantClientMock
.Verify(
x => x.DeleteAsync(
TestCollectionName,
It.Is<IReadOnlyList<ulong>>(x => x.Count == 2 && x.Contains(UlongTestRecordKey1) && x.Contains(UlongTestRecordKey2)),
true,
null,
null,
this._testCancellationToken),
Times.Once);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task CanDeleteManyGuidRecordsAsync(bool useDefinition, bool hasNamedVectors)
{
// Arrange
using var sut = this.CreateRecordCollection<Guid>(useDefinition, hasNamedVectors);
this.SetupDeleteMocks();
// Act
await sut.DeleteAsync(
[s_guidTestRecordKey1, s_guidTestRecordKey2],
cancellationToken: this._testCancellationToken);
// Assert
this._qdrantClientMock
.Verify(
x => x.DeleteAsync(
TestCollectionName,
It.Is<IReadOnlyList<Guid>>(x => x.Count == 2 && x.Contains(s_guidTestRecordKey1) && x.Contains(s_guidTestRecordKey2)),
true,
null,
null,
this._testCancellationToken),
Times.Once);
}
[Theory]
[MemberData(nameof(TestOptions))]
public async Task CanUpsertRecordAsync<TKey>(bool useDefinition, bool hasNamedVectors, TKey testRecordKey)
where TKey : notnull
{
// Arrange
using var sut = this.CreateRecordCollection<TKey>(useDefinition, hasNamedVectors);
this.SetupUpsertMock();
// Act
await sut.UpsertAsync(
CreateModel(testRecordKey, true),
cancellationToken: this._testCancellationToken);
// Assert
this._qdrantClientMock
.Verify(
x => x.UpsertAsync(
TestCollectionName,
It.Is<IReadOnlyList<PointStruct>>(x => x.Count == 1 && (testRecordKey!.GetType() == typeof(ulong) && x[0].Id.Num == (testRecordKey as ulong?) || testRecordKey!.GetType() == typeof(Guid) && x[0].Id.Uuid == (testRecordKey as Guid?).ToString())),
true,
null,
null,
this._testCancellationToken),
Times.Once);
}
[Theory]
[MemberData(nameof(MultiRecordTestOptions))]
public async Task CanUpsertManyRecordsAsync<TKey>(bool useDefinition, bool hasNamedVectors, TKey[] testRecordKeys)
where TKey : notnull
{
// Arrange
using var sut = this.CreateRecordCollection<TKey>(useDefinition, hasNamedVectors);
this.SetupUpsertMock();
var models = testRecordKeys.Select(x => CreateModel(x, true));
// Act
await sut.UpsertAsync(
models,
cancellationToken: this._testCancellationToken);
// Assert
this._qdrantClientMock
.Verify(
x => x.UpsertAsync(
TestCollectionName,
It.Is<IReadOnlyList<PointStruct>>(x =>
x.Count == 2 &&
(testRecordKeys[0]!.GetType() == typeof(ulong) && x[0].Id.Num == (testRecordKeys[0] as ulong?) || testRecordKeys[0]!.GetType() == typeof(Guid) && x[0].Id.Uuid == (testRecordKeys[0] as Guid?).ToString()) &&
(testRecordKeys[1]!.GetType() == typeof(ulong) && x[1].Id.Num == (testRecordKeys[1] as ulong?) || testRecordKeys[1]!.GetType() == typeof(Guid) && x[1].Id.Uuid == (testRecordKeys[1] as Guid?).ToString())),
true,
null,
null,
this._testCancellationToken),
Times.Once);
}
/// <summary>
/// Tests that the collection can be created even if the definition and the type do not match.
/// In this case, the expectation is that a custom mapper will be provided to map between the
/// schema as defined by the definition and the different data model.
/// </summary>
[Fact]
public void CanCreateCollectionWithMismatchedDefinitionAndType()
{
// Arrange.
var definition = new VectorStoreCollectionDefinition()
{
Properties =
[
new VectorStoreKeyProperty(nameof(SinglePropsModel<ulong>.Key), typeof(ulong)),
new VectorStoreDataProperty(nameof(SinglePropsModel<ulong>.OriginalNameData), typeof(string)),
new VectorStoreVectorProperty(nameof(SinglePropsModel<ulong>.Vector), typeof(ReadOnlyMemory<float>?), 4),
]
};
// Act.
using var sut = new QdrantCollection<ulong, SinglePropsModel<ulong>>(
() => this._qdrantClientMock.Object,
TestCollectionName,
new() { Definition = definition });
}
[Theory]
[MemberData(nameof(TestOptions))]
public async Task CanSearchWithVectorAndFilterAsync<TKey>(bool useDefinition, bool hasNamedVectors, TKey testRecordKey)
where TKey : notnull
{
using var sut = this.CreateRecordCollection<TKey>(useDefinition, hasNamedVectors);
// Arrange.
var scoredPoint = CreateScoredPoint(hasNamedVectors, testRecordKey);
this.SetupQueryMock([scoredPoint]);
// Act.
var results = await sut.SearchAsync(
new ReadOnlyMemory<float>(new[] { 1f, 2f, 3f, 4f }),
top: 5,
new() { IncludeVectors = true, Filter = r => r.Data == "data 1", Skip = 2 },
this._testCancellationToken).ToListAsync();
// Assert.
this._qdrantClientMock
.Verify(
x => x.QueryAsync(
TestCollectionName,
It.Is<Query?>(x => x!.Nearest.Dense.Data.ToArray().SequenceEqual(new[] { 1f, 2f, 3f, 4f })),
null,
hasNamedVectors ? "vector_storage_name" : null,
It.Is<Filter?>(x => x!.Must.Count == 1 && x.Must.First().Field.Key == "data_storage_name" && x.Must.First().Field.Match.Keyword == "data 1"),
null,
null,
5,
2,
null,
It.Is<WithVectorsSelector?>(x => x!.Enable == true),
null,
null,
null,
null,
this._testCancellationToken),
Times.Once);
Assert.Single(results);
Assert.Equal(testRecordKey, results.First().Record.Key);
Assert.Equal("data 1", results.First().Record.OriginalNameData);
Assert.Equal("data 1", results.First().Record.Data);
Assert.Equal(new float[] { 1, 2, 3, 4 }, results.First().Record.Vector!.Value.ToArray());
Assert.Equal(0.5f, results.First().Score);
}
private void SetupRetrieveMock(List<RetrievedPoint> retrievedPoints)
{
this._qdrantClientMock
.Setup(x => x.RetrieveAsync(
It.IsAny<string>(),
It.IsAny<IReadOnlyList<PointId>>(),
It.IsAny<bool>(), // With Payload
It.IsAny<bool>(), // With Vectors
It.IsAny<ReadConsistency>(),
It.IsAny<ShardKeySelector>(),
this._testCancellationToken))
.ReturnsAsync(retrievedPoints);
}
private void SetupQueryMock(List<ScoredPoint> scoredPoints)
{
this._qdrantClientMock
.Setup(x => x.QueryAsync(
It.IsAny<string>(),
It.IsAny<Query?>(),
null,
It.IsAny<string?>(),
It.IsAny<Filter?>(),
null,
null,
It.IsAny<ulong>(),
It.IsAny<ulong>(),
null,
It.IsAny<WithVectorsSelector?>(),
null,
null,
null,
null,
It.IsAny<CancellationToken>()))
.ReturnsAsync(scoredPoints);
}
private void SetupDeleteMocks()
{
this._qdrantClientMock
.Setup(x => x.DeleteAsync(
It.IsAny<string>(),
It.IsAny<ulong>(),
It.IsAny<bool>(), // wait
It.IsAny<WriteOrderingType?>(),
It.IsAny<ShardKeySelector?>(),
this._testCancellationToken))
.ReturnsAsync(new UpdateResult());
this._qdrantClientMock
.Setup(x => x.DeleteAsync(
It.IsAny<string>(),
It.IsAny<Guid>(),
It.IsAny<bool>(), // wait
It.IsAny<WriteOrderingType?>(),
It.IsAny<ShardKeySelector?>(),
this._testCancellationToken))
.ReturnsAsync(new UpdateResult());
this._qdrantClientMock
.Setup(x => x.DeleteAsync(
It.IsAny<string>(),
It.IsAny<IReadOnlyList<ulong>>(),
It.IsAny<bool>(), // wait
It.IsAny<WriteOrderingType?>(),
It.IsAny<ShardKeySelector?>(),
this._testCancellationToken))
.ReturnsAsync(new UpdateResult());
this._qdrantClientMock
.Setup(x => x.DeleteAsync(
It.IsAny<string>(),
It.IsAny<IReadOnlyList<Guid>>(),
It.IsAny<bool>(), // wait
It.IsAny<WriteOrderingType?>(),
It.IsAny<ShardKeySelector?>(),
this._testCancellationToken))
.ReturnsAsync(new UpdateResult());
}
private void SetupUpsertMock()
{
this._qdrantClientMock
.Setup(x => x.UpsertAsync(
It.IsAny<string>(),
It.IsAny<IReadOnlyList<PointStruct>>(),
It.IsAny<bool>(), // wait
It.IsAny<WriteOrderingType?>(),
It.IsAny<ShardKeySelector?>(),
this._testCancellationToken))
.ReturnsAsync(new UpdateResult());
}
private static RetrievedPoint CreateRetrievedPoint<TKey>(bool hasNamedVectors, TKey recordKey)
{
var responseVector = VectorOutput.Parser.ParseJson("{ \"data\": [1, 2, 3, 4] }");
RetrievedPoint point;
if (hasNamedVectors)
{
var namedVectors = new NamedVectorsOutput();
namedVectors.Vectors.Add("vector_storage_name", responseVector);
point = new RetrievedPoint()
{
Payload = { ["OriginalNameData"] = "data 1", ["data_storage_name"] = "data 1" },
Vectors = new VectorsOutput { Vectors = namedVectors }
};
}
else
{
point = new RetrievedPoint()
{
Payload = { ["OriginalNameData"] = "data 1", ["data_storage_name"] = "data 1" },
Vectors = new VectorsOutput() { Vector = responseVector }
};
}
if (recordKey is ulong ulongKey)
{
point.Id = ulongKey;
}
if (recordKey is Guid guidKey)
{
point.Id = guidKey;
}
return point;
}
private static ScoredPoint CreateScoredPoint<TKey>(bool hasNamedVectors, TKey recordKey)
{
var responseVector = VectorOutput.Parser.ParseJson("{ \"data\": [1, 2, 3, 4] }");
ScoredPoint point;
if (hasNamedVectors)
{
var namedVectors = new NamedVectorsOutput();
namedVectors.Vectors.Add("vector_storage_name", responseVector);
point = new ScoredPoint()
{
Score = 0.5f,
Payload = { ["OriginalNameData"] = "data 1", ["data_storage_name"] = "data 1" },
Vectors = new VectorsOutput { Vectors = namedVectors }
};
}
else
{
point = new ScoredPoint()
{
Score = 0.5f,
Payload = { ["OriginalNameData"] = "data 1", ["data_storage_name"] = "data 1" },
Vectors = new VectorsOutput() { Vector = responseVector }
};
}
if (recordKey is ulong ulongKey)
{
point.Id = ulongKey;
}
if (recordKey is Guid guidKey)
{
point.Id = guidKey;
}
return point;
}
private VectorStoreCollection<T, SinglePropsModel<T>> CreateRecordCollection<T>(bool useDefinition, bool hasNamedVectors)
where T : notnull
{
var store = new QdrantCollection<T, SinglePropsModel<T>>(
() => this._qdrantClientMock.Object,
TestCollectionName,
new()
{
Definition = useDefinition ? CreateSinglePropsDefinition(typeof(T)) : null,
HasNamedVectors = hasNamedVectors
}) as VectorStoreCollection<T, SinglePropsModel<T>>;
return store!;
}
private static SinglePropsModel<T> CreateModel<T>(T key, bool withVectors)
{
return new SinglePropsModel<T>
{
Key = key,
OriginalNameData = "data 1",
Data = "data 1",
Vector = withVectors ? new float[] { 1, 2, 3, 4 } : null,
NotAnnotated = null,
};
}
private static VectorStoreCollectionDefinition CreateSinglePropsDefinition(Type keyType)
{
return new()
{
Properties =
[
new VectorStoreKeyProperty("Key", keyType),
new VectorStoreDataProperty("OriginalNameData", typeof(string)) { IsIndexed = true, IsFullTextIndexed = true },
new VectorStoreDataProperty("Data", typeof(string)) { IsIndexed = true, StorageName = "data_storage_name" },
new VectorStoreVectorProperty("Vector", typeof(ReadOnlyMemory<float>), 4) { StorageName = "vector_storage_name" }
]
};
}
public sealed class SinglePropsModel<T>
{
[VectorStoreKey]
public required T Key { get; set; }
[VectorStoreData(IsIndexed = true, IsFullTextIndexed = true)]
public string OriginalNameData { get; set; } = string.Empty;
[JsonPropertyName("ignored_data_json_name")]
[VectorStoreData(IsIndexed = true, StorageName = "data_storage_name")]
public string Data { get; set; } = string.Empty;
[JsonPropertyName("ignored_vector_json_name")]
[VectorStoreVector(4, StorageName = "vector_storage_name")]
public ReadOnlyMemory<float>? Vector { get; set; }
public string? NotAnnotated { get; set; }
}
public static IEnumerable<object[]> TestOptions
=> GenerateAllCombinations(new object[][] {
new object[] { true, false },
new object[] { true, false },
new object[] { UlongTestRecordKey1, s_guidTestRecordKey1 }
});
public static IEnumerable<object[]> MultiRecordTestOptions
=> GenerateAllCombinations(new object[][] {
new object[] { true, false },
new object[] { true, false },
new object[] { new ulong[] { UlongTestRecordKey1, UlongTestRecordKey2 }, new Guid[] { s_guidTestRecordKey1, s_guidTestRecordKey2 } }
});
private static object[][] GenerateAllCombinations(object[][] input)
{
var counterArray = Enumerable.Range(0, input.Length).Select(x => 0).ToArray();
// Add each item from the first option set as a separate row.
object[][] currentCombinations = input[0].Select(x => new object[1] { x }).ToArray();
// Loop through each additional option set.
for (int currentOptionSetIndex = 1; currentOptionSetIndex < input.Length; currentOptionSetIndex++)
{
var iterationCombinations = new List<object[]>();
var currentOptionSet = input[currentOptionSetIndex];
// Loop through each row we have already.
foreach (var currentCombination in currentCombinations)
{
// Add each of the values from the new options set to the current row to generate a new row.
for (var currentColumnRow = 0; currentColumnRow < currentOptionSet.Length; currentColumnRow++)
{
iterationCombinations.Add(currentCombination.Append(currentOptionSet[currentColumnRow]).ToArray());
}
}
currentCombinations = iterationCombinations.ToArray();
}
return currentCombinations;
}
}
@@ -0,0 +1,455 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.Qdrant;
using Qdrant.Client.Grpc;
using Xunit;
namespace SemanticKernel.Connectors.Qdrant.UnitTests;
/// <summary>
/// Contains tests for the <see cref="QdrantMapper{TConsumerDataModel}"/> class.
/// </summary>
public class QdrantMapperTests
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MapsSinglePropsFromDataToStorageModelWithUlong(bool hasNamedVectors)
{
// Arrange.
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(ulong));
var model = new QdrantModelBuilder(hasNamedVectors)
.Build(typeof(SinglePropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<SinglePropsModel<ulong>>(model, hasNamedVectors);
// Act.
var actual = sut.MapFromDataToStorageModel(CreateSinglePropsModel<ulong>(5ul), recordIndex: 0, generatedEmbeddings: null);
// Assert.
Assert.NotNull(actual);
Assert.Equal(5ul, actual.Id.Num);
Assert.Single(actual.Payload);
Assert.Equal("data value", actual.Payload["data"].StringValue);
if (hasNamedVectors)
{
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vectors.Vectors_.Vectors["vector"].Data.ToArray());
}
else
{
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vectors.Vector.Data.ToArray());
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MapsSinglePropsFromDataToStorageModelWithGuid(bool hasNamedVectors)
{
// Arrange.
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(Guid));
var model = new QdrantModelBuilder(hasNamedVectors)
.Build(typeof(SinglePropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<SinglePropsModel<Guid>>(model, hasNamedVectors);
// Act.
var actual = sut.MapFromDataToStorageModel(CreateSinglePropsModel<Guid>(Guid.Parse("11111111-1111-1111-1111-111111111111")), recordIndex: 0, generatedEmbeddings: null);
// Assert.
Assert.NotNull(actual);
Assert.Equal(Guid.Parse("11111111-1111-1111-1111-111111111111"), Guid.Parse(actual.Id.Uuid));
Assert.Single(actual.Payload);
Assert.Equal("data value", actual.Payload["data"].StringValue);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void MapsSinglePropsFromStorageToDataModelWithUlong(bool hasNamedVectors, bool includeVectors)
{
// Arrange.
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(ulong));
var model = new QdrantModelBuilder(hasNamedVectors)
.Build(typeof(SinglePropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<SinglePropsModel<ulong>>(model, hasNamedVectors);
// Act.
var point = CreateSinglePropsPointStruct(5, hasNamedVectors);
var actual = sut.MapFromStorageToDataModel(point.Id, point.Payload, point.Vectors, includeVectors);
// Assert.
Assert.NotNull(actual);
Assert.Equal(5ul, actual.Key);
Assert.Equal("data value", actual.Data);
if (includeVectors)
{
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vector!.Value.ToArray());
}
else
{
Assert.Null(actual.Vector);
}
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void MapsSinglePropsFromStorageToDataModelWithGuid(bool hasNamedVectors, bool includeVectors)
{
// Arrange.
var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(Guid));
var model = new QdrantModelBuilder(hasNamedVectors)
.Build(typeof(SinglePropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<SinglePropsModel<Guid>>(model, hasNamedVectors);
// Act.
var point = CreateSinglePropsPointStruct(Guid.Parse("11111111-1111-1111-1111-111111111111"), hasNamedVectors);
var actual = sut.MapFromStorageToDataModel(point.Id, point.Payload, point.Vectors, includeVectors);
// Assert.
Assert.NotNull(actual);
Assert.Equal(Guid.Parse("11111111-1111-1111-1111-111111111111"), actual.Key);
Assert.Equal("data value", actual.Data);
if (includeVectors)
{
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vector!.Value.ToArray());
}
else
{
Assert.Null(actual.Vector);
}
}
[Fact]
public void MapsMultiPropsFromDataToStorageModelWithUlong()
{
// Arrange.
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(ulong));
var model = new QdrantModelBuilder(hasNamedVectors: true)
.Build(typeof(MultiPropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<MultiPropsModel<ulong>>(model, hasNamedVectors: true);
// Act.
var actual = sut.MapFromDataToStorageModel(CreateMultiPropsModel<ulong>(5ul), recordIndex: 0, generatedEmbeddings: null);
// Assert.
Assert.NotNull(actual);
Assert.Equal(5ul, actual.Id.Num);
Assert.Equal(8, actual.Payload.Count);
Assert.Equal("data 1", actual.Payload["dataString"].StringValue);
Assert.Equal(5, actual.Payload["dataInt"].IntegerValue);
Assert.Equal(5, actual.Payload["dataLong"].IntegerValue);
Assert.Equal(5.5f, actual.Payload["dataFloat"].DoubleValue);
Assert.Equal(5.5d, actual.Payload["dataDouble"].DoubleValue);
Assert.True(actual.Payload["dataBool"].BoolValue);
Assert.Equal("2025-02-10T05:10:15.0000000+01:00", actual.Payload["dataDateTimeOffset"].StringValue);
Assert.Equal(new int[] { 1, 2, 3, 4 }, actual.Payload["dataArrayInt"].ListValue.Values.Select(x => (int)x.IntegerValue).ToArray());
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vectors.Vectors_.Vectors["vector1"].Data.ToArray());
Assert.Equal(new float[] { 5, 6, 7, 8 }, actual.Vectors.Vectors_.Vectors["vector2"].Data.ToArray());
}
[Fact]
public void MapsMultiPropsFromDataToStorageModelWithGuid()
{
// Arrange.
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(Guid));
var model = new QdrantModelBuilder(hasNamedVectors: true)
.Build(typeof(MultiPropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<MultiPropsModel<Guid>>(model, hasNamedVectors: true);
// Act.
var actual = sut.MapFromDataToStorageModel(CreateMultiPropsModel<Guid>(Guid.Parse("11111111-1111-1111-1111-111111111111")), recordIndex: 0, generatedEmbeddings: null);
// Assert.
Assert.NotNull(actual);
Assert.Equal(Guid.Parse("11111111-1111-1111-1111-111111111111"), Guid.Parse(actual.Id.Uuid));
Assert.Equal(8, actual.Payload.Count);
Assert.Equal("data 1", actual.Payload["dataString"].StringValue);
Assert.Equal(5, actual.Payload["dataInt"].IntegerValue);
Assert.Equal(5, actual.Payload["dataLong"].IntegerValue);
Assert.Equal(5.5f, actual.Payload["dataFloat"].DoubleValue);
Assert.Equal(5.5d, actual.Payload["dataDouble"].DoubleValue);
Assert.True(actual.Payload["dataBool"].BoolValue);
Assert.Equal("2025-02-10T05:10:15.0000000+01:00", actual.Payload["dataDateTimeOffset"].StringValue);
Assert.Equal(new int[] { 1, 2, 3, 4 }, actual.Payload["dataArrayInt"].ListValue.Values.Select(x => (int)x.IntegerValue).ToArray());
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vectors.Vectors_.Vectors["vector1"].Data.ToArray());
Assert.Equal(new float[] { 5, 6, 7, 8 }, actual.Vectors.Vectors_.Vectors["vector2"].Data.ToArray());
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MapsMultiPropsFromStorageToDataModelWithUlong(bool includeVectors)
{
// Arrange.
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(ulong));
var model = new QdrantModelBuilder(hasNamedVectors: true)
.Build(typeof(MultiPropsModel<ulong>), typeof(ulong), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<MultiPropsModel<ulong>>(model, hasNamedVectors: true);
// Act.
var point = CreateMultiPropsPointStruct(5);
var actual = sut.MapFromStorageToDataModel(point.Id, point.Payload, point.Vectors, includeVectors);
// Assert.
Assert.NotNull(actual);
Assert.Equal(5ul, actual.Key);
Assert.Equal("data 1", actual.DataString);
Assert.Equal(5, actual.DataInt);
Assert.Equal(5L, actual.DataLong);
Assert.Equal(5.5f, actual.DataFloat);
Assert.Equal(5.5d, actual.DataDouble);
Assert.True(actual.DataBool);
Assert.Equal(new DateTimeOffset(2025, 2, 10, 5, 10, 15, TimeSpan.FromHours(1)), actual.DataDateTimeOffset);
Assert.Equal(new int[] { 1, 2, 3, 4 }, actual.DataArrayInt);
if (includeVectors)
{
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vector1!.Value.ToArray());
Assert.Equal(new float[] { 5, 6, 7, 8 }, actual.Vector2!.Value.ToArray());
}
else
{
Assert.Null(actual.Vector1);
Assert.Null(actual.Vector2);
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void MapsMultiPropsFromStorageToDataModelWithGuid(bool includeVectors)
{
// Arrange.
var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(Guid));
var model = new QdrantModelBuilder(hasNamedVectors: true)
.Build(typeof(MultiPropsModel<Guid>), typeof(Guid), definition, defaultEmbeddingGenerator: null);
var sut = new QdrantMapper<MultiPropsModel<Guid>>(model, hasNamedVectors: true);
// Act.
var point = CreateMultiPropsPointStruct(Guid.Parse("11111111-1111-1111-1111-111111111111"));
var actual = sut.MapFromStorageToDataModel(point.Id, point.Payload, point.Vectors, includeVectors);
// Assert.
Assert.NotNull(actual);
Assert.Equal(Guid.Parse("11111111-1111-1111-1111-111111111111"), actual.Key);
Assert.Equal("data 1", actual.DataString);
Assert.Equal(5, actual.DataInt);
Assert.Equal(5L, actual.DataLong);
Assert.Equal(5.5f, actual.DataFloat);
Assert.Equal(5.5d, actual.DataDouble);
Assert.True(actual.DataBool);
Assert.Equal(new DateTimeOffset(2025, 2, 10, 5, 10, 15, TimeSpan.FromHours(1)), actual.DataDateTimeOffset);
Assert.Equal(new int[] { 1, 2, 3, 4 }, actual.DataArrayInt);
if (includeVectors)
{
Assert.Equal(new float[] { 1, 2, 3, 4 }, actual.Vector1!.Value.ToArray());
Assert.Equal(new float[] { 5, 6, 7, 8 }, actual.Vector2!.Value.ToArray());
}
else
{
Assert.Null(actual.Vector1);
Assert.Null(actual.Vector2);
}
}
private static SinglePropsModel<TKey> CreateSinglePropsModel<TKey>(TKey key)
{
return new SinglePropsModel<TKey>
{
Key = key,
Data = "data value",
Vector = new float[] { 1, 2, 3, 4 },
NotAnnotated = "notAnnotated",
};
}
private static MultiPropsModel<TKey> CreateMultiPropsModel<TKey>(TKey key)
{
return new MultiPropsModel<TKey>
{
Key = key,
DataString = "data 1",
DataInt = 5,
DataLong = 5L,
DataFloat = 5.5f,
DataDouble = 5.5d,
DataBool = true,
DataDateTimeOffset = new DateTimeOffset(2025, 2, 10, 5, 10, 15, TimeSpan.FromHours(1)),
DataArrayInt = [1, 2, 3, 4],
Vector1 = new float[] { 1, 2, 3, 4 },
Vector2 = new float[] { 5, 6, 7, 8 },
NotAnnotated = "notAnnotated",
};
}
private static RetrievedPoint CreateSinglePropsPointStruct(ulong id, bool hasNamedVectors)
{
var pointStruct = new RetrievedPoint();
pointStruct.Id = new PointId() { Num = id };
AddDataToSinglePropsPointStruct(pointStruct, hasNamedVectors);
return pointStruct;
}
private static RetrievedPoint CreateSinglePropsPointStruct(Guid id, bool hasNamedVectors)
{
var pointStruct = new RetrievedPoint();
pointStruct.Id = new PointId() { Uuid = id.ToString() };
AddDataToSinglePropsPointStruct(pointStruct, hasNamedVectors);
return pointStruct;
}
private static void AddDataToSinglePropsPointStruct(RetrievedPoint pointStruct, bool hasNamedVectors)
{
var responseVector = VectorOutput.Parser.ParseJson("{ \"data\": [1, 2, 3, 4] }");
pointStruct.Payload.Add("data", "data value");
if (hasNamedVectors)
{
var namedVectors = new NamedVectorsOutput();
namedVectors.Vectors.Add("vector", responseVector);
pointStruct.Vectors = new VectorsOutput() { Vectors = namedVectors };
}
else
{
pointStruct.Vectors = new VectorsOutput() { Vector = responseVector };
}
}
private static RetrievedPoint CreateMultiPropsPointStruct(ulong id)
{
var pointStruct = new RetrievedPoint();
pointStruct.Id = new PointId() { Num = id };
AddDataToMultiPropsPointStruct(pointStruct);
return pointStruct;
}
private static RetrievedPoint CreateMultiPropsPointStruct(Guid id)
{
var pointStruct = new RetrievedPoint();
pointStruct.Id = new PointId() { Uuid = id.ToString() };
AddDataToMultiPropsPointStruct(pointStruct);
return pointStruct;
}
private static void AddDataToMultiPropsPointStruct(RetrievedPoint pointStruct)
{
pointStruct.Payload.Add("dataString", "data 1");
pointStruct.Payload.Add("dataInt", 5);
pointStruct.Payload.Add("dataLong", 5L);
pointStruct.Payload.Add("dataFloat", 5.5f);
pointStruct.Payload.Add("dataDouble", 5.5d);
pointStruct.Payload.Add("dataBool", true);
pointStruct.Payload.Add("dataDateTimeOffset", "2025-02-10T05:10:15.0000000+01:00");
var dataIntArray = new ListValue();
dataIntArray.Values.Add(1);
dataIntArray.Values.Add(2);
dataIntArray.Values.Add(3);
dataIntArray.Values.Add(4);
pointStruct.Payload.Add("dataArrayInt", new Value { ListValue = dataIntArray });
var responseVector1 = VectorOutput.Parser.ParseJson("{ \"data\": [1, 2, 3, 4] }");
var responseVector2 = VectorOutput.Parser.ParseJson("{ \"data\": [5, 6, 7, 8] }");
var namedVectors = new NamedVectorsOutput();
namedVectors.Vectors.Add("vector1", responseVector1);
namedVectors.Vectors.Add("vector2", responseVector2);
pointStruct.Vectors = new VectorsOutput() { Vectors = namedVectors };
}
private static VectorStoreCollectionDefinition CreateSinglePropsVectorStoreRecordDefinition(Type keyType) => new()
{
Properties =
[
new VectorStoreKeyProperty("Key", keyType) { StorageName = "key" },
new VectorStoreDataProperty("Data", typeof(string)) { StorageName = "data" },
new VectorStoreVectorProperty("Vector", typeof(ReadOnlyMemory<float>), 10) { StorageName = "vector" },
],
};
private sealed class SinglePropsModel<TKey>
{
[VectorStoreKey(StorageName = "key")]
public TKey? Key { get; set; } = default;
[VectorStoreData(StorageName = "data")]
public string Data { get; set; } = string.Empty;
[VectorStoreVector(10, StorageName = "vector")]
public ReadOnlyMemory<float>? Vector { get; set; }
public string NotAnnotated { get; set; } = string.Empty;
}
private static VectorStoreCollectionDefinition CreateMultiPropsVectorStoreRecordDefinition(Type keyType) => new()
{
Properties =
[
new VectorStoreKeyProperty("Key", keyType) { StorageName = "key" },
new VectorStoreDataProperty("DataString", typeof(string)) { StorageName = "dataString" },
new VectorStoreDataProperty("DataInt", typeof(int)) { StorageName = "dataInt" },
new VectorStoreDataProperty("DataLong", typeof(long)) { StorageName = "dataLong" },
new VectorStoreDataProperty("DataFloat", typeof(float)) { StorageName = "dataFloat" },
new VectorStoreDataProperty("DataDouble", typeof(double)) { StorageName = "dataDouble" },
new VectorStoreDataProperty("DataBool", typeof(bool)) { StorageName = "dataBool" },
new VectorStoreDataProperty("DataDateTimeOffset", typeof(DateTimeOffset)) { StorageName = "dataDateTimeOffset" },
new VectorStoreDataProperty("DataArrayInt", typeof(List<int>)) { StorageName = "dataArrayInt" },
new VectorStoreVectorProperty("Vector1", typeof(ReadOnlyMemory<float>), 10) { StorageName = "vector1" },
new VectorStoreVectorProperty("Vector2", typeof(ReadOnlyMemory<float>), 10) { StorageName = "vector2" },
],
};
private sealed class MultiPropsModel<TKey>
{
[VectorStoreKey(StorageName = "key")]
public TKey? Key { get; set; } = default;
[VectorStoreData(StorageName = "dataString")]
public string DataString { get; set; } = string.Empty;
[JsonPropertyName("data_int_json")]
[VectorStoreData(StorageName = "dataInt")]
public int DataInt { get; set; } = 0;
[VectorStoreData(StorageName = "dataLong")]
public long DataLong { get; set; } = 0;
[VectorStoreData(StorageName = "dataFloat")]
public float DataFloat { get; set; } = 0;
[VectorStoreData(StorageName = "dataDouble")]
public double DataDouble { get; set; } = 0;
[VectorStoreData(StorageName = "dataBool")]
public bool DataBool { get; set; } = false;
[VectorStoreData(StorageName = "dataDateTimeOffset")]
public DateTimeOffset DataDateTimeOffset { get; set; }
[VectorStoreData(StorageName = "dataArrayInt")]
public List<int>? DataArrayInt { get; set; }
[VectorStoreVector(10, StorageName = "vector1")]
public ReadOnlyMemory<float>? Vector1 { get; set; }
[VectorStoreVector(10, StorageName = "vector2")]
public ReadOnlyMemory<float>? Vector2 { get; set; }
public string NotAnnotated { get; set; } = string.Empty;
}
}
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.VectorData;
using Moq;
using Xunit;
namespace Microsoft.SemanticKernel.Connectors.Qdrant.UnitTests;
/// <summary>
/// Contains tests for the <see cref="QdrantVectorStore"/> class.
/// </summary>
public class QdrantVectorStoreTests
{
private const string TestCollectionName = "testcollection";
private readonly Mock<MockableQdrantClient> _qdrantClientMock;
private readonly CancellationToken _testCancellationToken = new(false);
public QdrantVectorStoreTests()
{
this._qdrantClientMock = new Mock<MockableQdrantClient>(MockBehavior.Strict);
}
[Fact]
public void GetCollectionReturnsCollection()
{
// Arrange.
using var sut = new QdrantVectorStore(this._qdrantClientMock.Object);
// Act.
var actual = sut.GetCollection<ulong, SinglePropsModel<ulong>>(TestCollectionName);
// Assert.
Assert.NotNull(actual);
Assert.IsType<QdrantCollection<ulong, SinglePropsModel<ulong>>>(actual);
}
[Fact]
public void GetCollectionThrowsForInvalidKeyType()
{
// Arrange.
using var sut = new QdrantVectorStore(this._qdrantClientMock.Object);
// Act & Assert.
Assert.Throws<NotSupportedException>(() => sut.GetCollection<string, SinglePropsModel<string>>(TestCollectionName));
}
[Fact]
public async Task ListCollectionNamesCallsSDKAsync()
{
// Arrange.
this._qdrantClientMock
.Setup(x => x.ListCollectionsAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new[] { "collection1", "collection2" });
using var sut = new QdrantVectorStore(this._qdrantClientMock.Object);
// Act.
var collectionNames = sut.ListCollectionNamesAsync(this._testCancellationToken);
// Assert.
var collectionNamesList = await collectionNames.ToListAsync();
Assert.Equal(new[] { "collection1", "collection2" }, collectionNamesList);
}
public sealed class SinglePropsModel<TKey>
{
[VectorStoreKey]
public required TKey Key { get; set; }
[VectorStoreData]
public string Data { get; set; } = string.Empty;
[VectorStoreVector(4)]
public ReadOnlyMemory<float>? Vector { get; set; }
public string? NotAnnotated { get; set; }
}
}