From 43235b810413824db057bb176ee96373c100c983 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:46:46 -0500 Subject: [PATCH] .Net: Refactored integration tests for vector stores (#9905) ### Motivation and Context Resolves: https://github.com/microsoft/semantic-kernel/issues/6459 This PR adds a base class for vector store integration tests and updates all integration tests for concrete vector store to use common workflow. ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone :smile: --- .../AzureAISearch/AzureAISearchHotel.cs | 48 +++++++++ .../AzureAISearchTextSearchTests.cs | 11 +- .../AzureAISearchVectorStoreFixture.cs | 52 ++------- ...ISearchVectorStoreRecordCollectionTests.cs | 49 +++++---- .../AzureAISearchVectorStoreTests.cs | 28 +---- .../AzureCosmosDBMongoDBHotel.cs | 48 +++++++++ .../AzureCosmosDBMongoDBVectorStoreFixture.cs | 41 ------- ...MongoDBVectorStoreRecordCollectionTests.cs | 1 - .../AzureCosmosDBMongoDBVectorStoreTests.cs | 16 +-- .../AzureCosmosDBNoSQLVectorStoreTests.cs | 18 +--- .../Connectors/Memory/BaseVectorStoreTests.cs | 47 ++++++++ .../Connectors/Memory/MongoDB/MongoDBHotel.cs | 48 +++++++++ .../MongoDB/MongoDBVectorStoreFixture.cs | 41 ------- ...MongoDBVectorStoreRecordCollectionTests.cs | 1 - .../Memory/MongoDB/MongoDBVectorStoreTests.cs | 16 +-- .../Pinecone/PineconeVectorStoreTests.cs | 10 +- .../Memory/Qdrant/QdrantVectorStoreTests.cs | 24 +---- ...HashSetVectorStoreRecordCollectionTests.cs | 81 +++++++------- .../Connectors/Memory/Redis/RedisHotel.cs | 102 ++++++++++++++++++ ...disJsonVectorStoreRecordCollectionTests.cs | 97 +++++++++-------- .../Memory/Redis/RedisVectorStoreFixture.cs | 100 +---------------- .../Memory/Redis/RedisVectorStoreTests.cs | 24 +---- .../Memory/Sqlite/SqliteVectorStoreTests.cs | 19 +--- .../Weaviate/WeaviateVectorStoreTests.cs | 29 +---- dotnet/src/IntegrationTests/testsettings.json | 3 +- 25 files changed, 453 insertions(+), 501 deletions(-) create mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs create mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBHotel.cs create mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs create mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs create mode 100644 dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs new file mode 100644 index 000000000000..3f979fe2b828 --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchHotel.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Text.Json.Serialization; +using Azure.Search.Documents.Indexes; +using Azure.Search.Documents.Indexes.Models; +using Microsoft.Extensions.VectorData; + +namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; + +#pragma warning disable CS8618 + +public class AzureAISearchHotel +{ + [SimpleField(IsKey = true, IsFilterable = true)] + [VectorStoreRecordKey] + public string HotelId { get; set; } + + [SearchableField(IsFilterable = true, IsSortable = true)] + [VectorStoreRecordData(IsFilterable = true, IsFullTextSearchable = true)] + public string HotelName { get; set; } + + [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] + [VectorStoreRecordData] + public string Description { get; set; } + + [VectorStoreRecordVector(1536)] + public ReadOnlyMemory? DescriptionEmbedding { get; set; } + + [SearchableField(IsFilterable = true, IsFacetable = true)] + [VectorStoreRecordData(IsFilterable = true)] +#pragma warning disable CA1819 // Properties should not return arrays + public string[] Tags { get; set; } +#pragma warning restore CA1819 // Properties should not return arrays + + [JsonPropertyName("parking_is_included")] + [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] + [VectorStoreRecordData(IsFilterable = true)] + public bool? ParkingIncluded { get; set; } + + [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] + [VectorStoreRecordData(IsFilterable = true)] + public DateTimeOffset? LastRenovationDate { get; set; } + + [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] + [VectorStoreRecordData] + public double? Rating { get; set; } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs index fcac923a277b..115ae9aabff5 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchTextSearchTests.cs @@ -10,7 +10,6 @@ using SemanticKernel.IntegrationTests.Data; using SemanticKernel.IntegrationTests.TestSettings; using Xunit; -using static SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch.AzureAISearchVectorStoreFixture; namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; @@ -82,11 +81,11 @@ public override Task CreateTextSearchAsync() this.VectorStore = new AzureAISearchVectorStore(fixture.SearchIndexClient); } - var vectorSearch = this.VectorStore.GetCollection(fixture.TestIndexName); + var vectorSearch = this.VectorStore.GetCollection(fixture.TestIndexName); var stringMapper = new HotelTextSearchStringMapper(); var resultMapper = new HotelTextSearchResultMapper(); - var result = new VectorStoreTextSearch(vectorSearch, this.EmbeddingGenerator!, stringMapper, resultMapper); + var result = new VectorStoreTextSearch(vectorSearch, this.EmbeddingGenerator!, stringMapper, resultMapper); return Task.FromResult(result); } @@ -105,7 +104,7 @@ public override bool VerifySearchResults(object[] results, string query, TextSea foreach (var result in results) { Assert.NotNull(result); - Assert.IsType(result); + Assert.IsType(result); } return true; @@ -119,7 +118,7 @@ protected sealed class HotelTextSearchStringMapper : ITextSearchStringMapper /// public string MapFromResultToString(object result) { - if (result is Hotel hotel) + if (result is AzureAISearchHotel hotel) { return $"{hotel.HotelName} {hotel.Description}"; } @@ -135,7 +134,7 @@ protected sealed class HotelTextSearchResultMapper : ITextSearchResultMapper /// public TextSearchResult MapFromResultToTextSearchResult(object result) { - if (result is Hotel hotel) + if (result is AzureAISearchHotel hotel) { return new TextSearchResult(value: hotel.Description) { Name = hotel.HotelName, Link = $"id://{hotel.HotelId}" }; } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs index 5fa7869e4a3a..0c247faeea57 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreFixture.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading.Tasks; using Azure; @@ -149,7 +148,7 @@ public static async Task CreateIndexAsync(string indexName, SearchIndexClient ad // Build the list of fields from the model, and then replace the DescriptionEmbedding field with a vector field, to work around // issue where the field is not recognized as an array on parsing on the server side when apply the VectorSearchFieldAttribute. FieldBuilder fieldBuilder = new(); - var searchFields = fieldBuilder.Build(typeof(Hotel)); + var searchFields = fieldBuilder.Build(typeof(AzureAISearchHotel)); var embeddingfield = searchFields.First(x => x.Name == "DescriptionEmbedding"); searchFields.Remove(embeddingfield); searchFields.Add(new VectorSearchField("DescriptionEmbedding", 1536, "my-vector-profile")); @@ -185,9 +184,9 @@ public static async Task UploadDocumentsAsync(SearchClient searchClient, ITextEm { var embedding = await embeddingGenerator.GenerateEmbeddingAsync("This is a great hotel"); - IndexDocumentsBatch batch = IndexDocumentsBatch.Create( + IndexDocumentsBatch batch = IndexDocumentsBatch.Create( IndexDocumentsAction.Upload( - new Hotel() + new AzureAISearchHotel() { HotelId = "BaseSet-1", HotelName = "Hotel 1", @@ -199,7 +198,7 @@ public static async Task UploadDocumentsAsync(SearchClient searchClient, ITextEm Rating = 3.6 }), IndexDocumentsAction.Upload( - new Hotel() + new AzureAISearchHotel() { HotelId = "BaseSet-2", HotelName = "Hotel 2", @@ -211,7 +210,7 @@ public static async Task UploadDocumentsAsync(SearchClient searchClient, ITextEm Rating = 3.60 }), IndexDocumentsAction.Upload( - new Hotel() + new AzureAISearchHotel() { HotelId = "BaseSet-3", HotelName = "Hotel 3", @@ -223,7 +222,7 @@ public static async Task UploadDocumentsAsync(SearchClient searchClient, ITextEm Rating = 4.80 }), IndexDocumentsAction.Upload( - new Hotel() + new AzureAISearchHotel() { HotelId = "BaseSet-4", HotelName = "Hotel 4", @@ -241,43 +240,4 @@ public static async Task UploadDocumentsAsync(SearchClient searchClient, ITextEm // Add some delay to allow time for the documents to get indexed and show up in search. await Task.Delay(5000); } - -#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - public class Hotel - { - [SimpleField(IsKey = true, IsFilterable = true)] - [VectorStoreRecordKey] - public string HotelId { get; set; } - - [SearchableField(IsFilterable = true, IsSortable = true)] - [VectorStoreRecordData(IsFilterable = true, IsFullTextSearchable = true)] - public string HotelName { get; set; } - - [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] - [VectorStoreRecordData] - public string Description { get; set; } - - [VectorStoreRecordVector(1536)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } - - [SearchableField(IsFilterable = true, IsFacetable = true)] - [VectorStoreRecordData(IsFilterable = true)] -#pragma warning disable CA1819 // Properties should not return arrays - public string[] Tags { get; set; } -#pragma warning restore CA1819 // Properties should not return arrays - - [JsonPropertyName("parking_is_included")] - [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] - [VectorStoreRecordData(IsFilterable = true)] - public bool? ParkingIncluded { get; set; } - - [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] - [VectorStoreRecordData(IsFilterable = true)] - public DateTimeOffset? LastRenovationDate { get; set; } - - [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] - [VectorStoreRecordData] - public double? Rating { get; set; } - } -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs index 9265efa90f02..e3a420a789f4 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreRecordCollectionTests.cs @@ -11,7 +11,6 @@ using Microsoft.SemanticKernel.Embeddings; using Xunit; using Xunit.Abstractions; -using static SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch.AzureAISearchVectorStoreFixture; namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; @@ -32,7 +31,7 @@ public async Task CollectionExistsReturnsCollectionStateAsync(bool expectedExist { // Arrange. var collectionName = expectedExists ? fixture.TestIndexName : "nonexistentcollection"; - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, collectionName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, collectionName); // Act. var actual = await sut.CollectionExistsAsync(); @@ -49,11 +48,11 @@ public async Task ItCanCreateACollectionUpsertGetAndSearchAsync(bool useRecordDe // Arrange var hotel = await this.CreateTestHotelAsync("Upsert-1"); var testCollectionName = $"{fixture.TestIndexName}-createtest"; - var options = new AzureAISearchVectorStoreRecordCollectionOptions + var options = new AzureAISearchVectorStoreRecordCollectionOptions { VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, testCollectionName, options); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, testCollectionName, options); await sut.DeleteCollectionAsync(); @@ -112,7 +111,7 @@ public async Task ItCanDeleteCollectionAsync() // Arrange var tempCollectionName = fixture.TestIndexName + "-delete"; await AzureAISearchVectorStoreFixture.CreateIndexAsync(tempCollectionName, fixture.SearchIndexClient); - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, tempCollectionName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, tempCollectionName); // Act await sut.DeleteCollectionAsync(); @@ -127,11 +126,11 @@ public async Task ItCanDeleteCollectionAsync() public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition) { // Arrange - var options = new AzureAISearchVectorStoreRecordCollectionOptions + var options = new AzureAISearchVectorStoreRecordCollectionOptions { VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); // Act var hotel = await this.CreateTestHotelAsync("Upsert-1"); @@ -161,7 +160,7 @@ public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition public async Task ItCanUpsertManyDocumentsToVectorStoreAsync() { // Arrange - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); // Act var results = sut.UpsertBatchAsync( @@ -195,11 +194,11 @@ await this.CreateTestHotelAsync("UpsertMany-3"), public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool useRecordDefinition) { // Arrange - var options = new AzureAISearchVectorStoreRecordCollectionOptions + var options = new AzureAISearchVectorStoreRecordCollectionOptions { VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); // Act var getResult = await sut.GetAsync("BaseSet-1", new GetRecordOptions { IncludeVectors = includeVectors }); @@ -232,7 +231,7 @@ public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool public async Task ItCanGetManyDocumentsFromVectorStoreAsync() { // Arrange - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); // Act // Also include one non-existing key to test that the operation does not fail for these and returns only the found ones. @@ -256,11 +255,11 @@ public async Task ItCanGetManyDocumentsFromVectorStoreAsync() public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefinition) { // Arrange - var options = new AzureAISearchVectorStoreRecordCollectionOptions + var options = new AzureAISearchVectorStoreRecordCollectionOptions { VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); await sut.UpsertAsync(await this.CreateTestHotelAsync("Remove-1")); // Act @@ -276,7 +275,7 @@ public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefiniti public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() { // Arrange - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); await sut.UpsertAsync(await this.CreateTestHotelAsync("RemoveMany-1")); await sut.UpsertAsync(await this.CreateTestHotelAsync("RemoveMany-2")); await sut.UpsertAsync(await this.CreateTestHotelAsync("RemoveMany-3")); @@ -295,7 +294,7 @@ public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() { // Arrange - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); // Act & Assert Assert.Null(await sut.GetAsync("BaseSet-5", new GetRecordOptions { IncludeVectors = true })); @@ -306,7 +305,7 @@ public async Task ItThrowsOperationExceptionForFailedConnectionAsync() { // Arrange var searchIndexClient = new SearchIndexClient(new Uri("https://localhost:12345"), new AzureKeyCredential("12345")); - var sut = new AzureAISearchVectorStoreRecordCollection(searchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(searchIndexClient, fixture.TestIndexName); // Act & Assert await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-1", new GetRecordOptions { IncludeVectors = true })); @@ -317,7 +316,7 @@ public async Task ItThrowsOperationExceptionForFailedAuthenticationAsync() { // Arrange var searchIndexClient = new SearchIndexClient(new Uri(fixture.Config.ServiceUrl), new AzureKeyCredential("12345")); - var sut = new AzureAISearchVectorStoreRecordCollection(searchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(searchIndexClient, fixture.TestIndexName); // Act & Assert await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-1", new GetRecordOptions { IncludeVectors = true })); @@ -327,8 +326,8 @@ public async Task ItThrowsOperationExceptionForFailedAuthenticationAsync() public async Task ItThrowsMappingExceptionForFailedMapperAsync() { // Arrange - var options = new AzureAISearchVectorStoreRecordCollectionOptions { JsonObjectCustomMapper = new FailingMapper() }; - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); + var options = new AzureAISearchVectorStoreRecordCollectionOptions { JsonObjectCustomMapper = new FailingMapper() }; + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName, options); // Act & Assert await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-1", new GetRecordOptions { IncludeVectors = true })); @@ -340,7 +339,7 @@ public async Task ItThrowsMappingExceptionForFailedMapperAsync() public async Task ItCanSearchWithVectorAndFiltersAsync(string option, bool includeVectors) { // Arrange. - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); // Act. var filter = option == "equality" ? new VectorSearchFilter().EqualTo("HotelName", "Hotel 3") : new VectorSearchFilter().AnyTagEqualTo("Tags", "bar"); @@ -380,7 +379,7 @@ await fixture.EmbeddingGenerator.GenerateEmbeddingAsync("A great hotel"), public async Task ItCanSearchWithVectorizableTextAndFiltersAsync() { // Arrange. - var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); + var sut = new AzureAISearchVectorStoreRecordCollection(fixture.SearchIndexClient, fixture.TestIndexName); // Act. var filter = new VectorSearchFilter().EqualTo("HotelName", "Hotel 3"); @@ -452,7 +451,7 @@ public async Task ItCanUpsertAndRetrieveUsingTheGenericMapperAsync() Assert.Equal(genericMapperEmbedding, (ReadOnlyMemory)localGetResult.Vectors["DescriptionEmbedding"]!); } - private async Task CreateTestHotelAsync(string hotelId) => new() + private async Task CreateTestHotelAsync(string hotelId) => new() { HotelId = hotelId, HotelName = $"MyHotel {hotelId}", @@ -464,14 +463,14 @@ public async Task ItCanUpsertAndRetrieveUsingTheGenericMapperAsync() Rating = 3.6 }; - private sealed class FailingMapper : IVectorStoreRecordMapper + private sealed class FailingMapper : IVectorStoreRecordMapper { - public JsonObject MapFromDataToStorageModel(Hotel dataModel) + public JsonObject MapFromDataToStorageModel(AzureAISearchHotel dataModel) { throw new NotImplementedException(); } - public Hotel MapFromStorageToDataModel(JsonObject storageModel, StorageToDataModelMapperOptions options) + public AzureAISearchHotel MapFromStorageToDataModel(JsonObject storageModel, StorageToDataModelMapperOptions options) { throw new NotImplementedException(); } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs index 7bda8cb0fff9..6afcc439faf0 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureAISearch/AzureAISearchVectorStoreTests.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Linq; using System.Threading.Tasks; using Microsoft.SemanticKernel.Connectors.AzureAISearch; using Xunit; -using Xunit.Abstractions; namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; @@ -14,32 +11,15 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureAISearch; /// Tests work with an Azure AI Search Instance. /// [Collection("AzureAISearchVectorStoreCollection")] -public class AzureAISearchVectorStoreTests(ITestOutputHelper output, AzureAISearchVectorStoreFixture fixture) +public class AzureAISearchVectorStoreTests(AzureAISearchVectorStoreFixture fixture) + : BaseVectorStoreTests(new AzureAISearchVectorStore(fixture.SearchIndexClient)) { // If null, all tests will be enabled private const string SkipReason = "Requires Azure AI Search Service instance up and running"; [Fact(Skip = SkipReason)] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() + public override async Task ItCanGetAListOfExistingCollectionNamesAsync() { - // Arrange - var additionalCollectionName = fixture.TestIndexName + "-listnames"; - await AzureAISearchVectorStoreFixture.DeleteIndexIfExistsAsync(additionalCollectionName, fixture.SearchIndexClient); - await AzureAISearchVectorStoreFixture.CreateIndexAsync(additionalCollectionName, fixture.SearchIndexClient); - var sut = new AzureAISearchVectorStore(fixture.SearchIndexClient); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Equal(2, collectionNames.Where(x => x.StartsWith(fixture.TestIndexName, StringComparison.InvariantCultureIgnoreCase)).Count()); - Assert.Contains(fixture.TestIndexName, collectionNames); - Assert.Contains(additionalCollectionName, collectionNames); - - // Output - output.WriteLine(string.Join(",", collectionNames)); - - // Cleanup - await AzureAISearchVectorStoreFixture.DeleteIndexIfExistsAsync(additionalCollectionName, fixture.SearchIndexClient); + await base.ItCanGetAListOfExistingCollectionNamesAsync(); } } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBHotel.cs new file mode 100644 index 000000000000..7a8830ea2842 --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBHotel.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.VectorData; + +namespace SemanticKernel.IntegrationTests.Connectors.AzureCosmosDBMongoDB; + +#pragma warning disable CS8618 + +public class AzureCosmosDBMongoDBHotel +{ + /// The key of the record. + [VectorStoreRecordKey] + public string HotelId { get; init; } + + /// A string metadata field. + [VectorStoreRecordData(IsFilterable = true)] + public string? HotelName { get; set; } + + /// An int metadata field. + [VectorStoreRecordData] + public int HotelCode { get; set; } + + /// A float metadata field. + [VectorStoreRecordData] + public float? HotelRating { get; set; } + + /// A bool metadata field. + [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] + public bool ParkingIncluded { get; set; } + + /// An array metadata field. + [VectorStoreRecordData] + public List Tags { get; set; } = []; + + /// A data field. + [VectorStoreRecordData] + public string Description { get; set; } + + /// A datetime metadata field. + [VectorStoreRecordData] + public DateTime Timestamp { get; set; } + + /// A vector field. + [VectorStoreRecordVector(Dimensions: 4, DistanceFunction: DistanceFunction.CosineDistance, IndexKind: IndexKind.IvfFlat)] + public ReadOnlyMemory? DescriptionEmbedding { get; set; } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreFixture.cs index 36ce5c0ca321..a56f8b41399c 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreFixture.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreFixture.cs @@ -81,47 +81,6 @@ public async Task DisposeAsync() } } -#pragma warning disable CS8618 - public record AzureCosmosDBMongoDBHotel() - { - /// The key of the record. - [VectorStoreRecordKey] - public string HotelId { get; init; } - - /// A string metadata field. - [VectorStoreRecordData(IsFilterable = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreRecordData] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreRecordData] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] - public bool ParkingIncluded { get; set; } - - /// An array metadata field. - [VectorStoreRecordData] - public List Tags { get; set; } = []; - - /// A data field. - [VectorStoreRecordData] - public string Description { get; set; } - - /// A datetime metadata field. - [VectorStoreRecordData] - public DateTime Timestamp { get; set; } - - /// A vector field. - [VectorStoreRecordVector(Dimensions: 4, DistanceFunction: DistanceFunction.CosineDistance, IndexKind: IndexKind.IvfFlat)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } - } -#pragma warning restore CS8618 - #region private private static string GetConnectionString(IConfigurationRoot configuration) diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs index c936a92cf11c..c5929e0ecaa2 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreRecordCollectionTests.cs @@ -9,7 +9,6 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using Xunit; -using static SemanticKernel.IntegrationTests.Connectors.AzureCosmosDBMongoDB.AzureCosmosDBMongoDBVectorStoreFixture; namespace SemanticKernel.IntegrationTests.Connectors.AzureCosmosDBMongoDB; diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreTests.cs index 9be1378b7b86..9fcbcf81083a 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBMongoDB/AzureCosmosDBMongoDBVectorStoreTests.cs @@ -1,29 +1,21 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Linq; using System.Threading.Tasks; using Microsoft.SemanticKernel.Connectors.AzureCosmosDBMongoDB; +using SemanticKernel.IntegrationTests.Connectors.Memory; using Xunit; namespace SemanticKernel.IntegrationTests.Connectors.AzureCosmosDBMongoDB; [Collection("AzureCosmosDBMongoDBVectorStoreCollection")] public class AzureCosmosDBMongoDBVectorStoreTests(AzureCosmosDBMongoDBVectorStoreFixture fixture) + : BaseVectorStoreTests(new AzureCosmosDBMongoDBVectorStore(fixture.MongoDatabase)) { private const string? SkipReason = "Azure CosmosDB MongoDB cluster is required"; [Fact(Skip = SkipReason)] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() + public override async Task ItCanGetAListOfExistingCollectionNamesAsync() { - // Arrange - var sut = new AzureCosmosDBMongoDBVectorStore(fixture.MongoDatabase); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Contains("sk-test-hotels", collectionNames); - Assert.Contains("sk-test-contacts", collectionNames); - Assert.Contains("sk-test-addresses", collectionNames); + await base.ItCanGetAListOfExistingCollectionNamesAsync(); } } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBNoSQL/AzureCosmosDBNoSQLVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBNoSQL/AzureCosmosDBNoSQLVectorStoreTests.cs index 938fe5c14caf..4d3899784f4a 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBNoSQL/AzureCosmosDBNoSQLVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/AzureCosmosDBNoSQL/AzureCosmosDBNoSQLVectorStoreTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Linq; using System.Threading.Tasks; -using Microsoft.Azure.Cosmos; using Microsoft.SemanticKernel.Connectors.AzureCosmosDBNoSQL; using Xunit; @@ -13,23 +11,13 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory.AzureCosmosDBNoSQL; /// [Collection("AzureCosmosDBNoSQLVectorStoreCollection")] public sealed class AzureCosmosDBNoSQLVectorStoreTests(AzureCosmosDBNoSQLVectorStoreFixture fixture) + : BaseVectorStoreTests(new AzureCosmosDBNoSQLVectorStore(fixture.Database!)) { private const string? SkipReason = "Azure CosmosDB NoSQL cluster is required"; [Fact(Skip = SkipReason)] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() + public override async Task ItCanGetAListOfExistingCollectionNamesAsync() { - // Arrange - var sut = new AzureCosmosDBNoSQLVectorStore(fixture.Database!); - - await fixture.Database!.CreateContainerIfNotExistsAsync(new ContainerProperties("list-names-1", "/id")); - await fixture.Database!.CreateContainerIfNotExistsAsync(new ContainerProperties("list-names-2", "/id")); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Contains("list-names-1", collectionNames); - Assert.Contains("list-names-2", collectionNames); + await base.ItCanGetAListOfExistingCollectionNamesAsync(); } } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs new file mode 100644 index 000000000000..1d7739fd427d --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Memory/BaseVectorStoreTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.VectorData; +using Xunit; + +namespace SemanticKernel.IntegrationTests.Connectors.Memory; + +/// +/// Base class for integration tests. +/// +public abstract class BaseVectorStoreTests(IVectorStore vectorStore) + where TKey : notnull +{ + [Fact] + public virtual async Task ItCanGetAListOfExistingCollectionNamesAsync() + { + // Arrange + var expectedCollectionNames = new List { "listcollectionnames1", "listcollectionnames2", "listcollectionnames3" }; + + foreach (var collectionName in expectedCollectionNames) + { + var collection = vectorStore.GetCollection(collectionName); + + await collection.CreateCollectionIfNotExistsAsync(); + } + + // Act + var actualCollectionNames = await vectorStore.ListCollectionNamesAsync().ToListAsync(); + + // Assert + var expected = expectedCollectionNames.Select(l => l.ToUpperInvariant()).ToList(); + var actual = actualCollectionNames.Select(l => l.ToUpperInvariant()).ToList(); + + expected.ForEach(item => Assert.Contains(item, actual)); + + // Cleanup + foreach (var collectionName in expectedCollectionNames) + { + var collection = vectorStore.GetCollection(collectionName); + + await collection.DeleteCollectionAsync(); + } + } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs new file mode 100644 index 000000000000..b3adb2e723a1 --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBHotel.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.VectorData; + +namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; + +#pragma warning disable CS8618 + +public class MongoDBHotel +{ + /// The key of the record. + [VectorStoreRecordKey] + public string HotelId { get; init; } + + /// A string metadata field. + [VectorStoreRecordData(IsFilterable = true)] + public string? HotelName { get; set; } + + /// An int metadata field. + [VectorStoreRecordData] + public int HotelCode { get; set; } + + /// A float metadata field. + [VectorStoreRecordData] + public float? HotelRating { get; set; } + + /// A bool metadata field. + [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] + public bool ParkingIncluded { get; set; } + + /// An array metadata field. + [VectorStoreRecordData] + public List Tags { get; set; } = []; + + /// A data field. + [VectorStoreRecordData] + public string Description { get; set; } + + /// A datetime metadata field. + [VectorStoreRecordData] + public DateTime Timestamp { get; set; } + + /// A vector field. + [VectorStoreRecordVector(Dimensions: 4, DistanceFunction: DistanceFunction.CosineSimilarity, IndexKind: IndexKind.IvfFlat)] + public ReadOnlyMemory? DescriptionEmbedding { get; set; } +} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs index 6c037c70e11b..3d975dffbdf3 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreFixture.cs @@ -88,47 +88,6 @@ public async Task DisposeAsync() } } -#pragma warning disable CS8618 - public record MongoDBHotel() - { - /// The key of the record. - [VectorStoreRecordKey] - public string HotelId { get; init; } - - /// A string metadata field. - [VectorStoreRecordData(IsFilterable = true)] - public string? HotelName { get; set; } - - /// An int metadata field. - [VectorStoreRecordData] - public int HotelCode { get; set; } - - /// A float metadata field. - [VectorStoreRecordData] - public float? HotelRating { get; set; } - - /// A bool metadata field. - [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] - public bool ParkingIncluded { get; set; } - - /// An array metadata field. - [VectorStoreRecordData] - public List Tags { get; set; } = []; - - /// A data field. - [VectorStoreRecordData] - public string Description { get; set; } - - /// A datetime metadata field. - [VectorStoreRecordData] - public DateTime Timestamp { get; set; } - - /// A vector field. - [VectorStoreRecordVector(Dimensions: 4, DistanceFunction: DistanceFunction.CosineSimilarity, IndexKind: IndexKind.IvfFlat)] - public ReadOnlyMemory? DescriptionEmbedding { get; set; } - } -#pragma warning restore CS8618 - #region private private static async Task SetupMongoDBContainerAsync(DockerClient client) diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs index b0d6affb384f..11da55ba3329 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreRecordCollectionTests.cs @@ -9,7 +9,6 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using Xunit; -using static SemanticKernel.IntegrationTests.Connectors.MongoDB.MongoDBVectorStoreFixture; namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs index e4d29d5925ce..cd0d7e374c4c 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/MongoDB/MongoDBVectorStoreTests.cs @@ -1,30 +1,22 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Linq; using System.Threading.Tasks; using Microsoft.SemanticKernel.Connectors.MongoDB; +using SemanticKernel.IntegrationTests.Connectors.Memory; using Xunit; namespace SemanticKernel.IntegrationTests.Connectors.MongoDB; [Collection("MongoDBVectorStoreCollection")] public class MongoDBVectorStoreTests(MongoDBVectorStoreFixture fixture) + : BaseVectorStoreTests(new MongoDBVectorStore(fixture.MongoDatabase)) { // If null, all tests will be enabled private const string? SkipReason = "The tests are for manual verification."; [Fact(Skip = SkipReason)] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() + public override async Task ItCanGetAListOfExistingCollectionNamesAsync() { - // Arrange - var sut = new MongoDBVectorStore(fixture.MongoDatabase); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Contains("sk-test-hotels", collectionNames); - Assert.Contains("sk-test-contacts", collectionNames); - Assert.Contains("sk-test-addresses", collectionNames); + await base.ItCanGetAListOfExistingCollectionNamesAsync(); } } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Pinecone/PineconeVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Pinecone/PineconeVectorStoreTests.cs index f1f1c6e63937..2864bf28b793 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Pinecone/PineconeVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Pinecone/PineconeVectorStoreTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.VectorData; using Microsoft.SemanticKernel.Connectors.Pinecone; @@ -13,16 +12,15 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory.Pinecone; [Collection("PineconeVectorStoreTests")] [PineconeApiKeySetCondition] -public class PineconeVectorStoreTests(PineconeVectorStoreFixture fixture) : IClassFixture +public class PineconeVectorStoreTests(PineconeVectorStoreFixture fixture) + : BaseVectorStoreTests(new PineconeVectorStore(fixture.Client)), IClassFixture { private PineconeVectorStoreFixture Fixture { get; } = fixture; [PineconeFact] - public async Task ListCollectionNamesAsync() + public override async Task ItCanGetAListOfExistingCollectionNamesAsync() { - var collectionNames = await this.Fixture.VectorStore.ListCollectionNamesAsync().ToListAsync(); - - Assert.Equal([this.Fixture.IndexName], collectionNames); + await base.ItCanGetAListOfExistingCollectionNamesAsync(); } [PineconeFact] diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs index ae4a1313bfee..39551054e4bb 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Qdrant/QdrantVectorStoreTests.cs @@ -1,15 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Linq; using System.Threading.Tasks; using Microsoft.SemanticKernel.Connectors.Qdrant; using Xunit; -using Xunit.Abstractions; namespace SemanticKernel.IntegrationTests.Connectors.Memory.Qdrant; [Collection("QdrantVectorStoreCollection")] -public class QdrantVectorStoreTests(ITestOutputHelper output, QdrantVectorStoreFixture fixture) +public class QdrantVectorStoreTests(QdrantVectorStoreFixture fixture) + : BaseVectorStoreTests(new QdrantVectorStore(fixture.QdrantClient)) { [Fact] public async Task ItPassesSettingsFromVectorStoreToCollectionAsync() @@ -34,23 +33,4 @@ await directCollection.UpsertAsync(new QdrantVectorStoreFixture.HotelInfo DescriptionEmbedding = new float[1536], }); } - - [Fact] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() - { - // Arrange - var sut = new QdrantVectorStore(fixture.QdrantClient); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Equal(3, collectionNames.Count); - Assert.Contains("namedVectorsHotels", collectionNames); - Assert.Contains("singleVectorHotels", collectionNames); - Assert.Contains("singleVectorGuidIdHotels", collectionNames); - - // Output - output.WriteLine(string.Join(",", collectionNames)); - } } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs index 4fff25413c5c..6e60f8bb12f0 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHashSetVectorStoreRecordCollectionTests.cs @@ -10,7 +10,6 @@ using StackExchange.Redis; using Xunit; using Xunit.Abstractions; -using static SemanticKernel.IntegrationTests.Connectors.Memory.Redis.RedisVectorStoreFixture; namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; @@ -33,7 +32,7 @@ public sealed class RedisHashSetVectorStoreRecordCollectionTests(ITestOutputHelp public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) { // Arrange. - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, collectionName); + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, collectionName); // Act. var actual = await sut.CollectionExistsAsync(); @@ -52,12 +51,12 @@ public async Task ItCanCreateACollectionUpsertGetAndSearchAsync(bool useRecordDe var collectionNamePostfix = useRecordDefinition ? "WithDefinition" : "WithType"; var testCollectionName = $"hashsetcreatetest{collectionNamePostfix}"; - var options = new RedisHashSetVectorStoreRecordCollectionOptions + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, testCollectionName, options); + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, testCollectionName, options); // Act await sut.CreateCollectionAsync(); @@ -111,7 +110,7 @@ public async Task ItCanDeleteCollectionAsync() createParams.AddPrefix(tempCollectionName); await fixture.Database.FT().CreateAsync(tempCollectionName, createParams, schema); - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, tempCollectionName); + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, tempCollectionName); // Act await sut.DeleteCollectionAsync(); @@ -126,12 +125,12 @@ public async Task ItCanDeleteCollectionAsync() public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition) { // Arrange. - var options = new RedisHashSetVectorStoreRecordCollectionOptions + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); var record = CreateTestHotel("HUpsert-2", 2); // Act. @@ -159,12 +158,12 @@ public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition public async Task ItCanUpsertManyDocumentsToVectorStoreAsync(bool useRecordDefinition) { // Arrange. - var options = new RedisHashSetVectorStoreRecordCollectionOptions + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act. var results = sut.UpsertBatchAsync( @@ -198,12 +197,12 @@ public async Task ItCanUpsertManyDocumentsToVectorStoreAsync(bool useRecordDefin public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool useRecordDefinition) { // Arrange. - var options = new RedisHashSetVectorStoreRecordCollectionOptions + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act. var getResult = await sut.GetAsync("HBaseSet-1", new GetRecordOptions { IncludeVectors = includeVectors }); @@ -232,8 +231,8 @@ public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool public async Task ItCanGetManyDocumentsFromVectorStoreAsync() { // Arrange - var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act // Also include one non-existing key to test that the operation does not fail for these and returns only the found ones. @@ -257,13 +256,13 @@ public async Task ItCanGetManyDocumentsFromVectorStoreAsync() public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefinition) { // Arrange. - var options = new RedisHashSetVectorStoreRecordCollectionOptions + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.BasicVectorStoreRecordDefinition : null }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); - var record = new BasicFloat32Hotel + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var record = new RedisBasicFloat32Hotel { HotelId = "HRemove-1", HotelName = "Remove Test Hotel", @@ -287,8 +286,8 @@ public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefiniti public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() { // Arrange - var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); await sut.UpsertAsync(CreateTestHotel("HRemoveMany-1", 1)); await sut.UpsertAsync(CreateTestHotel("HRemoveMany-2", 2)); await sut.UpsertAsync(CreateTestHotel("HRemoveMany-3", 3)); @@ -309,8 +308,8 @@ public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() public async Task ItCanSearchWithFloat32VectorAndFilterAsync(string filterType, bool includeVectors) { // Arrange - var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); var vector = new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }); var filter = filterType == "equality" ? new VectorSearchFilter().EqualTo("HotelCode", 1) : new VectorSearchFilter().EqualTo("HotelName", "My Hotel 1"); @@ -348,14 +347,14 @@ public async Task ItCanSearchWithFloat32VectorAndFilterAsync(string filterType, public async Task ItCanSearchWithFloat32VectorAndTopSkipAsync() { // Arrange - var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName + "TopSkip", options); + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName + "TopSkip", options); await sut.CreateCollectionIfNotExistsAsync(); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "HTopSkip_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "HTopSkip_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 2.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "HTopSkip_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 3.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "HTopSkip_4", HotelName = "4", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 4.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "HTopSkip_5", HotelName = "5", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 5.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 2.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 3.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_4", HotelName = "4", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 4.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "HTopSkip_5", HotelName = "5", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 5.0f]) }); var vector = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]); // Act @@ -379,12 +378,12 @@ public async Task ItCanSearchWithFloat32VectorAndTopSkipAsync() public async Task ItCanSearchWithFloat64VectorAsync(bool includeVectors) { // Arrange - var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName + "Float64", options); + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName + "Float64", options); await sut.CreateCollectionIfNotExistsAsync(); - await sut.UpsertAsync(new BasicFloat64Hotel { HotelId = "HFloat64_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0d, 1.1d, 1.2d, 1.3d]) }); - await sut.UpsertAsync(new BasicFloat64Hotel { HotelId = "HFloat64_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]) }); - await sut.UpsertAsync(new BasicFloat64Hotel { HotelId = "HFloat64_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([3.0d, 3.1d, 3.2d, 3.3d]) }); + await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "HFloat64_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0d, 1.1d, 1.2d, 1.3d]) }); + await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "HFloat64_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]) }); + await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "HFloat64_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([3.0d, 3.1d, 3.2d, 3.3d]) }); var vector = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]); @@ -418,8 +417,8 @@ public async Task ItCanSearchWithFloat64VectorAsync(bool includeVectors) public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() { // Arrange - var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act & Assert Assert.Null(await sut.GetAsync("HBaseSet-5", new GetRecordOptions { IncludeVectors = true })); @@ -429,12 +428,12 @@ public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() public async Task ItThrowsMappingExceptionForFailedMapperAsync() { // Arrange - var options = new RedisHashSetVectorStoreRecordCollectionOptions + var options = new RedisHashSetVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, HashEntriesCustomMapper = new FailingMapper() }; - var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var sut = new RedisHashSetVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act & Assert await Assert.ThrowsAsync(async () => await sut.GetAsync("HBaseSet-1", new GetRecordOptions { IncludeVectors = true })); @@ -493,9 +492,9 @@ public async Task ItCanUpsertAndRetrieveUsingTheGenericMapperAsync() Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult.Vectors["DescriptionEmbedding"]!).ToArray()); } - private static BasicFloat32Hotel CreateTestHotel(string hotelId, int hotelCode) + private static RedisBasicFloat32Hotel CreateTestHotel(string hotelId, int hotelCode) { - var record = new BasicFloat32Hotel + var record = new RedisBasicFloat32Hotel { HotelId = hotelId, HotelName = $"My Hotel {hotelCode}", @@ -508,14 +507,14 @@ private static BasicFloat32Hotel CreateTestHotel(string hotelId, int hotelCode) return record; } - private sealed class FailingMapper : IVectorStoreRecordMapper + private sealed class FailingMapper : IVectorStoreRecordMapper { - public (string Key, HashEntry[] HashEntries) MapFromDataToStorageModel(BasicFloat32Hotel dataModel) + public (string Key, HashEntry[] HashEntries) MapFromDataToStorageModel(RedisBasicFloat32Hotel dataModel) { throw new NotImplementedException(); } - public BasicFloat32Hotel MapFromStorageToDataModel((string Key, HashEntry[] HashEntries) storageModel, StorageToDataModelMapperOptions options) + public RedisBasicFloat32Hotel MapFromStorageToDataModel((string Key, HashEntry[] HashEntries) storageModel, StorageToDataModelMapperOptions options) { throw new NotImplementedException(); } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs new file mode 100644 index 000000000000..87dc5c2fb89b --- /dev/null +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisHotel.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Text.Json.Serialization; +using Microsoft.Extensions.VectorData; + +namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +/// +/// A test model for the vector store that has complex properties as supported by JSON redis mode. +/// +public class RedisHotel +{ + [VectorStoreRecordKey] + public string HotelId { get; init; } + + [VectorStoreRecordData(IsFilterable = true)] + public string HotelName { get; init; } + + [VectorStoreRecordData(IsFilterable = true)] + public int HotelCode { get; init; } + + [VectorStoreRecordData(IsFullTextSearchable = true)] + public string Description { get; init; } + + [VectorStoreRecordVector(4)] + public ReadOnlyMemory? DescriptionEmbedding { get; init; } + +#pragma warning disable CA1819 // Properties should not return arrays + [VectorStoreRecordData(IsFilterable = true)] + public string[] Tags { get; init; } + + [VectorStoreRecordData(IsFullTextSearchable = true)] + public string[] FTSTags { get; init; } +#pragma warning restore CA1819 // Properties should not return arrays + + [JsonPropertyName("parking_is_included")] + [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] + public bool ParkingIncluded { get; init; } + + [VectorStoreRecordData] + public DateTimeOffset LastRenovationDate { get; init; } + + [VectorStoreRecordData] + public double Rating { get; init; } + + [VectorStoreRecordData] + public RedisHotelAddress Address { get; init; } +} + +/// +/// A test model for the vector store to simulate a complex type. +/// +public class RedisHotelAddress +{ + public string City { get; init; } + public string Country { get; init; } +} + +/// +/// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. +/// +public class RedisBasicHotel +{ + [VectorStoreRecordKey] + public string HotelId { get; init; } + + [VectorStoreRecordData(IsFilterable = true)] + public string HotelName { get; init; } + + [VectorStoreRecordData(IsFilterable = true)] + public int HotelCode { get; init; } + + [VectorStoreRecordData(IsFullTextSearchable = true)] + public string Description { get; init; } + + [VectorStoreRecordVector(4)] + public ReadOnlyMemory? DescriptionEmbedding { get; init; } + + [JsonPropertyName("parking_is_included")] + [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] + public bool ParkingIncluded { get; init; } + + [VectorStoreRecordData] + public double Rating { get; init; } +} + +/// +/// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. +/// +public class RedisBasicFloat32Hotel : RedisBasicHotel +{ +} + +/// +/// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. +/// +public class RedisBasicFloat64Hotel : RedisBasicHotel +{ +} diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs index 780a88067b61..7bb4ad04fa9f 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisJsonVectorStoreRecordCollectionTests.cs @@ -10,7 +10,6 @@ using NRedisStack.Search; using Xunit; using Xunit.Abstractions; -using static SemanticKernel.IntegrationTests.Connectors.Memory.Redis.RedisVectorStoreFixture; namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; @@ -33,7 +32,7 @@ public sealed class RedisJsonVectorStoreRecordCollectionTests(ITestOutputHelper public async Task CollectionExistsReturnsCollectionStateAsync(string collectionName, bool expectedExists) { // Arrange. - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, collectionName); + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, collectionName); // Act. var actual = await sut.CollectionExistsAsync(); @@ -52,12 +51,12 @@ public async Task ItCanCreateACollectionUpsertGetAndSearchAsync(bool useRecordDe var collectionNamePostfix = useRecordDefinition ? "WithDefinition" : "WithType"; var testCollectionName = $"jsoncreatetest{collectionNamePostfix}"; - var options = new RedisJsonVectorStoreRecordCollectionOptions + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, testCollectionName, options); + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, testCollectionName, options); // Act await sut.CreateCollectionAsync(); @@ -120,7 +119,7 @@ public async Task ItCanDeleteCollectionAsync() createParams.AddPrefix(tempCollectionName); await fixture.Database.FT().CreateAsync(tempCollectionName, createParams, schema); - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, tempCollectionName); + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, tempCollectionName); // Act await sut.DeleteCollectionAsync(); @@ -135,13 +134,13 @@ public async Task ItCanDeleteCollectionAsync() public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition) { // Arrange. - var options = new RedisJsonVectorStoreRecordCollectionOptions + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); - Hotel record = CreateTestHotel("Upsert-2", 2); + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + RedisHotel record = CreateTestHotel("Upsert-2", 2); // Act. var upsertResult = await sut.UpsertAsync(record); @@ -173,12 +172,12 @@ public async Task ItCanUpsertDocumentToVectorStoreAsync(bool useRecordDefinition public async Task ItCanUpsertManyDocumentsToVectorStoreAsync(bool useRecordDefinition) { // Arrange. - var options = new RedisJsonVectorStoreRecordCollectionOptions + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act. var results = sut.UpsertBatchAsync( @@ -212,12 +211,12 @@ public async Task ItCanUpsertManyDocumentsToVectorStoreAsync(bool useRecordDefin public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool useRecordDefinition) { // Arrange. - var options = new RedisJsonVectorStoreRecordCollectionOptions + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act. var getResult = await sut.GetAsync("BaseSet-1", new GetRecordOptions { IncludeVectors = includeVectors }); @@ -250,8 +249,8 @@ public async Task ItCanGetDocumentFromVectorStoreAsync(bool includeVectors, bool public async Task ItCanGetManyDocumentsFromVectorStoreAsync() { // Arrange - var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act // Also include one non-existing key to test that the operation does not fail for these and returns only the found ones. @@ -273,8 +272,8 @@ public async Task ItCanGetManyDocumentsFromVectorStoreAsync() public async Task ItFailsToGetDocumentsWithInvalidSchemaAsync() { // Arrange. - var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act & Assert. await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-4-Invalid", new GetRecordOptions { IncludeVectors = true })); @@ -286,14 +285,14 @@ public async Task ItFailsToGetDocumentsWithInvalidSchemaAsync() public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefinition) { // Arrange. - var options = new RedisJsonVectorStoreRecordCollectionOptions + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, VectorStoreRecordDefinition = useRecordDefinition ? fixture.VectorStoreRecordDefinition : null }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); - var address = new HotelAddress { City = "Seattle", Country = "USA" }; - var record = new Hotel + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var address = new RedisHotelAddress { City = "Seattle", Country = "USA" }; + var record = new RedisHotel { HotelId = "Remove-1", HotelName = "Remove Test Hotel", @@ -317,8 +316,8 @@ public async Task ItCanRemoveDocumentFromVectorStoreAsync(bool useRecordDefiniti public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() { // Arrange - var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); await sut.UpsertAsync(CreateTestHotel("RemoveMany-1", 1)); await sut.UpsertAsync(CreateTestHotel("RemoveMany-2", 2)); await sut.UpsertAsync(CreateTestHotel("RemoveMany-3", 3)); @@ -339,8 +338,8 @@ public async Task ItCanRemoveManyDocumentsFromVectorStoreAsync() public async Task ItCanSearchWithFloat32VectorAndFilterAsync(string filterType) { // Arrange - var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); var vector = new ReadOnlyMemory(new[] { 30f, 31f, 32f, 33f }); var filter = filterType == "equality" ? new VectorSearchFilter().EqualTo("HotelCode", 1) : new VectorSearchFilter().AnyTagEqualTo("Tags", "pool"); @@ -372,14 +371,14 @@ public async Task ItCanSearchWithFloat32VectorAndFilterAsync(string filterType) public async Task ItCanSearchWithFloat32VectorAndTopSkipAsync() { // Arrange - var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName + "TopSkip", options); + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName + "TopSkip", options); await sut.CreateCollectionIfNotExistsAsync(); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "TopSkip_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "TopSkip_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 2.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "TopSkip_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 3.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "TopSkip_4", HotelName = "4", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 4.0f]) }); - await sut.UpsertAsync(new BasicFloat32Hotel { HotelId = "TopSkip_5", HotelName = "5", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 5.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 2.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 3.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_4", HotelName = "4", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 4.0f]) }); + await sut.UpsertAsync(new RedisBasicFloat32Hotel { HotelId = "TopSkip_5", HotelName = "5", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 5.0f]) }); var vector = new ReadOnlyMemory([1.0f, 1.0f, 1.0f, 1.0f]); // Act @@ -403,12 +402,12 @@ public async Task ItCanSearchWithFloat32VectorAndTopSkipAsync() public async Task ItCanSearchWithFloat64VectorAsync(bool includeVectors) { // Arrange - var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName + "Float64", options); + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName + "Float64", options); await sut.CreateCollectionIfNotExistsAsync(); - await sut.UpsertAsync(new BasicFloat64Hotel { HotelId = "Float64_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0d, 1.1d, 1.2d, 1.3d]) }); - await sut.UpsertAsync(new BasicFloat64Hotel { HotelId = "Float64_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]) }); - await sut.UpsertAsync(new BasicFloat64Hotel { HotelId = "Float64_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([3.0d, 3.1d, 3.2d, 3.3d]) }); + await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "Float64_1", HotelName = "1", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([1.0d, 1.1d, 1.2d, 1.3d]) }); + await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "Float64_2", HotelName = "2", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]) }); + await sut.UpsertAsync(new RedisBasicFloat64Hotel { HotelId = "Float64_3", HotelName = "3", Description = "Nice hotel", DescriptionEmbedding = new ReadOnlyMemory([3.0d, 3.1d, 3.2d, 3.3d]) }); var vector = new ReadOnlyMemory([2.0d, 2.1d, 2.2d, 2.3d]); @@ -438,8 +437,8 @@ public async Task ItCanSearchWithFloat64VectorAsync(bool includeVectors) public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() { // Arrange - var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true }; + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act & Assert Assert.Null(await sut.GetAsync("BaseSet-5", new GetRecordOptions { IncludeVectors = true })); @@ -449,12 +448,12 @@ public async Task ItReturnsNullWhenGettingNonExistentRecordAsync() public async Task ItThrowsMappingExceptionForFailedMapperAsync() { // Arrange - var options = new RedisJsonVectorStoreRecordCollectionOptions + var options = new RedisJsonVectorStoreRecordCollectionOptions { PrefixCollectionNameToKeyNames = true, JsonNodeCustomMapper = new FailingMapper() }; - var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); + var sut = new RedisJsonVectorStoreRecordCollection(fixture.Database, TestCollectionName, options); // Act & Assert await Assert.ThrowsAsync(async () => await sut.GetAsync("BaseSet-1", new GetRecordOptions { IncludeVectors = true })); @@ -484,7 +483,7 @@ public async Task ItCanUpsertAndRetrieveUsingTheGenericMapperAsync() { "ParkingIncluded", true }, { "LastRenovationDate", new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero) }, { "Rating", 3.6 }, - { "Address", new HotelAddress { City = "Seattle", Country = "USA" } }, + { "Address", new RedisHotelAddress { City = "Seattle", Country = "USA" } }, { "Description", "This is a generic mapper hotel" }, { "DescriptionEmbedding", new[] { 30f, 31f, 32f, 33f } } }, @@ -505,7 +504,7 @@ public async Task ItCanUpsertAndRetrieveUsingTheGenericMapperAsync() Assert.True((bool)baseSetGetResult.Data["ParkingIncluded"]!); Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), baseSetGetResult.Data["LastRenovationDate"]); Assert.Equal(3.6, baseSetGetResult.Data["Rating"]); - Assert.Equal("Seattle", ((HotelAddress)baseSetGetResult.Data["Address"]!).City); + Assert.Equal("Seattle", ((RedisHotelAddress)baseSetGetResult.Data["Address"]!).City); Assert.Equal("This is a great hotel.", baseSetGetResult.Data["Description"]); Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)baseSetGetResult.Vectors["DescriptionEmbedding"]!).ToArray()); @@ -520,15 +519,15 @@ public async Task ItCanUpsertAndRetrieveUsingTheGenericMapperAsync() Assert.True((bool)localGetResult.Data["ParkingIncluded"]!); Assert.Equal(new DateTimeOffset(1970, 1, 18, 0, 0, 0, TimeSpan.Zero), localGetResult.Data["LastRenovationDate"]); Assert.Equal(3.6d, localGetResult.Data["Rating"]); - Assert.Equal("Seattle", ((HotelAddress)localGetResult.Data["Address"]!).City); + Assert.Equal("Seattle", ((RedisHotelAddress)localGetResult.Data["Address"]!).City); Assert.Equal("This is a generic mapper hotel", localGetResult.Data["Description"]); Assert.Equal(new[] { 30f, 31f, 32f, 33f }, ((ReadOnlyMemory)localGetResult.Vectors["DescriptionEmbedding"]!).ToArray()); } - private static Hotel CreateTestHotel(string hotelId, int hotelCode) + private static RedisHotel CreateTestHotel(string hotelId, int hotelCode) { - var address = new HotelAddress { City = "Seattle", Country = "USA" }; - var record = new Hotel + var address = new RedisHotelAddress { City = "Seattle", Country = "USA" }; + var record = new RedisHotel { HotelId = hotelId, HotelName = $"My Hotel {hotelCode}", @@ -545,14 +544,14 @@ private static Hotel CreateTestHotel(string hotelId, int hotelCode) return record; } - private sealed class FailingMapper : IVectorStoreRecordMapper + private sealed class FailingMapper : IVectorStoreRecordMapper { - public (string Key, JsonNode Node) MapFromDataToStorageModel(Hotel dataModel) + public (string Key, JsonNode Node) MapFromDataToStorageModel(RedisHotel dataModel) { throw new NotImplementedException(); } - public Hotel MapFromStorageToDataModel((string Key, JsonNode Node) storageModel, StorageToDataModelMapperOptions options) + public RedisHotel MapFromStorageToDataModel((string Key, JsonNode Node) storageModel, StorageToDataModelMapperOptions options) { throw new NotImplementedException(); } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs index 26ea2338001f..bec643a13d5b 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreFixture.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Text.Json.Serialization; using System.Threading.Tasks; using Docker.DotNet; using Docker.DotNet.Models; @@ -17,6 +16,7 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + /// /// Does setup and teardown of redis docker container and associated test data. /// @@ -49,7 +49,7 @@ public RedisVectorStoreFixture() new VectorStoreRecordDataProperty("ParkingIncluded", typeof(bool)) { StoragePropertyName = "parking_is_included" }, new VectorStoreRecordDataProperty("LastRenovationDate", typeof(DateTimeOffset)), new VectorStoreRecordDataProperty("Rating", typeof(double)), - new VectorStoreRecordDataProperty("Address", typeof(HotelAddress)) + new VectorStoreRecordDataProperty("Address", typeof(RedisHotelAddress)) } }; this.BasicVectorStoreRecordDefinition = new VectorStoreRecordDefinition @@ -120,7 +120,7 @@ public async Task InitializeAsync() await this.Database.FT().CreateAsync("hashhotels", hashsetCreateParams, hashSchema); // Create some test data. - var address = new HotelAddress { City = "Seattle", Country = "USA" }; + var address = new RedisHotelAddress { City = "Seattle", Country = "USA" }; var embedding = new[] { 30f, 31f, 32f, 33f }; // Add JSON test data. @@ -234,98 +234,4 @@ await client.Containers.StartContainerAsync( return container.ID; } - - /// - /// A test model for the vector store that has complex properties as supported by JSON redis mode. - /// - public class Hotel - { - [VectorStoreRecordKey] - public string HotelId { get; init; } - - [VectorStoreRecordData(IsFilterable = true)] - public string HotelName { get; init; } - - [VectorStoreRecordData(IsFilterable = true)] - public int HotelCode { get; init; } - - [VectorStoreRecordData(IsFullTextSearchable = true)] - public string Description { get; init; } - - [VectorStoreRecordVector(4)] - public ReadOnlyMemory? DescriptionEmbedding { get; init; } - -#pragma warning disable CA1819 // Properties should not return arrays - [VectorStoreRecordData(IsFilterable = true)] - public string[] Tags { get; init; } - - [VectorStoreRecordData(IsFullTextSearchable = true)] - public string[] FTSTags { get; init; } -#pragma warning restore CA1819 // Properties should not return arrays - - [JsonPropertyName("parking_is_included")] - [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] - public bool ParkingIncluded { get; init; } - - [VectorStoreRecordData] - public DateTimeOffset LastRenovationDate { get; init; } - - [VectorStoreRecordData] - public double Rating { get; init; } - - [VectorStoreRecordData] - public HotelAddress Address { get; init; } - } - - /// - /// A test model for the vector store to simulate a complex type. - /// - public class HotelAddress - { - public string City { get; init; } - public string Country { get; init; } - } - - /// - /// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. - /// - public class BasicHotel - { - [VectorStoreRecordKey] - public string HotelId { get; init; } - - [VectorStoreRecordData(IsFilterable = true)] - public string HotelName { get; init; } - - [VectorStoreRecordData(IsFilterable = true)] - public int HotelCode { get; init; } - - [VectorStoreRecordData(IsFullTextSearchable = true)] - public string Description { get; init; } - - [VectorStoreRecordVector(4)] - public ReadOnlyMemory? DescriptionEmbedding { get; init; } - - [JsonPropertyName("parking_is_included")] - [VectorStoreRecordData(StoragePropertyName = "parking_is_included")] - public bool ParkingIncluded { get; init; } - - [VectorStoreRecordData] - public double Rating { get; init; } - } - - /// - /// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. - /// - public class BasicFloat32Hotel : BasicHotel - { - } - - /// - /// A test model for the vector store that only uses basic types as supported by HashSets Redis mode. - /// - public class BasicFloat64Hotel : BasicHotel - { - } } -#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs index 8e18522928eb..edde28dea285 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Redis/RedisVectorStoreTests.cs @@ -1,39 +1,25 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Linq; using System.Threading.Tasks; using Microsoft.SemanticKernel.Connectors.Redis; using Xunit; -using Xunit.Abstractions; namespace SemanticKernel.IntegrationTests.Connectors.Memory.Redis; /// /// Contains tests for the class. /// -/// Used to write to the test output stream. /// The test fixture. [Collection("RedisVectorStoreCollection")] -public class RedisVectorStoreTests(ITestOutputHelper output, RedisVectorStoreFixture fixture) +public class RedisVectorStoreTests(RedisVectorStoreFixture fixture) + : BaseVectorStoreTests(new RedisVectorStore(fixture.Database)) { // If null, all tests will be enabled - private const string SkipReason = "Requires Redis docker container up and running"; + private const string SkipReason = "This test is for manual verification"; [Fact(Skip = SkipReason)] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() + public override async Task ItCanGetAListOfExistingCollectionNamesAsync() { - // Arrange - var sut = new RedisVectorStore(fixture.Database); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Equal(2, collectionNames.Count); - Assert.Contains("jsonhotels", collectionNames); - Assert.Contains("hashhotels", collectionNames); - - // Output - output.WriteLine(string.Join(",", collectionNames)); + await base.ItCanGetAListOfExistingCollectionNamesAsync(); } } diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Sqlite/SqliteVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Sqlite/SqliteVectorStoreTests.cs index dc23b633b5b7..755f79195a93 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Sqlite/SqliteVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Sqlite/SqliteVectorStoreTests.cs @@ -15,27 +15,14 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory.Sqlite; /// [Collection("SqliteVectorStoreCollection")] public sealed class SqliteVectorStoreTests(SqliteVectorStoreFixture fixture) + : BaseVectorStoreTests>(new SqliteVectorStore(fixture.Connection!)) { private const string? SkipReason = "SQLite vector search extension is required"; [Fact(Skip = SkipReason)] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() + public override async Task ItCanGetAListOfExistingCollectionNamesAsync() { - // Arrange - var sut = new SqliteVectorStore(fixture.Connection!); - - var collection1 = fixture.GetCollection>("ListCollectionNames1"); - var collection2 = fixture.GetCollection>("ListCollectionNames2"); - - await collection1.CreateCollectionIfNotExistsAsync(); - await collection2.CreateCollectionIfNotExistsAsync(); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Contains("ListCollectionNames1", collectionNames); - Assert.Contains("ListCollectionNames1", collectionNames); + await base.ItCanGetAListOfExistingCollectionNamesAsync(); } [Fact(Skip = SkipReason)] diff --git a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs index 7de9413084ae..ce278486e9bc 100644 --- a/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/Memory/Weaviate/WeaviateVectorStoreTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Linq; -using System.Threading.Tasks; +using System; using Microsoft.SemanticKernel.Connectors.Weaviate; using Xunit; @@ -9,27 +8,5 @@ namespace SemanticKernel.IntegrationTests.Connectors.Memory.Weaviate; [Collection("WeaviateVectorStoreCollection")] public sealed class WeaviateVectorStoreTests(WeaviateVectorStoreFixture fixture) -{ - [Fact] - public async Task ItCanGetAListOfExistingCollectionNamesAsync() - { - // Arrange - var collection1 = new WeaviateVectorStoreRecordCollection(fixture.HttpClient!, "Collection1"); - var collection2 = new WeaviateVectorStoreRecordCollection(fixture.HttpClient!, "Collection2"); - var collection3 = new WeaviateVectorStoreRecordCollection(fixture.HttpClient!, "Collection3"); - - await collection1.CreateCollectionAsync(); - await collection2.CreateCollectionAsync(); - await collection3.CreateCollectionAsync(); - - var sut = new WeaviateVectorStore(fixture.HttpClient!); - - // Act - var collectionNames = await sut.ListCollectionNamesAsync().ToListAsync(); - - // Assert - Assert.Contains("Collection1", collectionNames); - Assert.Contains("Collection2", collectionNames); - Assert.Contains("Collection3", collectionNames); - } -} + : BaseVectorStoreTests(new WeaviateVectorStore(fixture.HttpClient!)) +{ } diff --git a/dotnet/src/IntegrationTests/testsettings.json b/dotnet/src/IntegrationTests/testsettings.json index c2396c7c0419..22c91e9affcc 100644 --- a/dotnet/src/IntegrationTests/testsettings.json +++ b/dotnet/src/IntegrationTests/testsettings.json @@ -95,7 +95,8 @@ "VectorSearchCollection": "dotnetMSKNearestTest.nearestSearch" }, "AzureCosmosDBNoSQL": { - "ConnectionString": "" + "ConnectionString": "", + "Endpoint": "" }, "AzureCosmosDBMongoDB": { "ConnectionString": ""