// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using Azure.Search.Documents.Indexes.Models; using Microsoft.Extensions.VectorData; using Microsoft.Extensions.VectorData.ProviderServices; using Microsoft.SemanticKernel.Connectors.AzureAISearch; using Xunit; namespace SemanticKernel.Connectors.AzureAISearch.UnitTests; /// /// Contains tests for the class. /// public class AzureAISearchCollectionCreateMappingTests { [Fact] public void MapKeyFieldCreatesSearchableField() { // Arrange var keyProperty = new KeyPropertyModel("testkey", typeof(string)) { StorageName = "test_key" }; // Act var result = AzureAISearchCollectionCreateMapping.MapKeyField(keyProperty); // Assert Assert.NotNull(result); Assert.Equal("test_key", result.Name); Assert.True(result.IsKey); Assert.True(result.IsFilterable); } [Theory] [InlineData(true)] [InlineData(false)] public void MapFilterableStringDataFieldCreatesSimpleField(bool isFilterable) { // Arrange var dataProperty = new DataPropertyModel("testdata", typeof(string)) { IsIndexed = isFilterable, StorageName = "test_data" }; // Act var result = AzureAISearchCollectionCreateMapping.MapDataField(dataProperty); // Assert Assert.NotNull(result); Assert.IsType(result); Assert.Equal("test_data", result.Name); Assert.False(result.IsKey); Assert.Equal(isFilterable, result.IsFilterable); } [Theory] [InlineData(true)] [InlineData(false)] public void MapFullTextSearchableStringDataFieldCreatesSearchableField(bool isFilterable) { // Arrange var dataProperty = new DataPropertyModel("testdata", typeof(string)) { IsIndexed = isFilterable, IsFullTextIndexed = true, StorageName = "test_data" }; // Act var result = AzureAISearchCollectionCreateMapping.MapDataField(dataProperty); // Assert Assert.NotNull(result); Assert.IsType(result); Assert.Equal("test_data", result.Name); Assert.False(result.IsKey); Assert.Equal(isFilterable, result.IsFilterable); } [Fact] public void MapFullTextSearchableStringDataFieldThrowsForInvalidType() { // Arrange var dataProperty = new DataPropertyModel("testdata", typeof(int)) { IsFullTextIndexed = true, StorageName = "test_data" }; // Act & Assert Assert.Throws(() => AzureAISearchCollectionCreateMapping.MapDataField(dataProperty)); } [Theory] [InlineData(true)] [InlineData(false)] public void MapDataFieldCreatesSimpleField(bool isFilterable) { // Arrange var dataProperty = new DataPropertyModel("testdata", typeof(int)) { IsIndexed = isFilterable, StorageName = "test_data" }; // Act var result = AzureAISearchCollectionCreateMapping.MapDataField(dataProperty); // Assert Assert.NotNull(result); Assert.IsType(result); Assert.Equal("test_data", result.Name); Assert.Equal(SearchFieldDataType.Int32, result.Type); Assert.False(result.IsKey); Assert.Equal(isFilterable, result.IsFilterable); } [Fact] public void MapVectorFieldCreatesVectorSearchField() { // Arrange var vectorProperty = new VectorPropertyModel("testvector", typeof(ReadOnlyMemory)) { Dimensions = 10, IndexKind = IndexKind.Flat, DistanceFunction = DistanceFunction.DotProductSimilarity, StorageName = "test_vector" }; // Act var (vectorSearchField, algorithmConfiguration, vectorSearchProfile) = AzureAISearchCollectionCreateMapping.MapVectorField(vectorProperty); // Assert Assert.NotNull(vectorSearchField); Assert.NotNull(algorithmConfiguration); Assert.NotNull(vectorSearchProfile); Assert.Equal("test_vector", vectorSearchField.Name); Assert.Equal(vectorProperty.Dimensions, vectorSearchField.VectorSearchDimensions); Assert.Equal("test_vectorAlgoConfig", algorithmConfiguration.Name); Assert.IsType(algorithmConfiguration); var flatConfig = algorithmConfiguration as ExhaustiveKnnAlgorithmConfiguration; Assert.Equal(VectorSearchAlgorithmMetric.DotProduct, flatConfig!.Parameters.Metric); Assert.Equal("test_vectorProfile", vectorSearchProfile.Name); Assert.Equal("test_vectorAlgoConfig", vectorSearchProfile.AlgorithmConfigurationName); } [Theory] [InlineData(IndexKind.Hnsw, typeof(HnswAlgorithmConfiguration))] [InlineData(IndexKind.Flat, typeof(ExhaustiveKnnAlgorithmConfiguration))] public void MapVectorFieldCreatesExpectedAlgoConfigTypes(string indexKind, Type algoConfigType) { // Arrange var vectorProperty = new VectorPropertyModel("testvector", typeof(ReadOnlyMemory)) { Dimensions = 10, IndexKind = indexKind, DistanceFunction = DistanceFunction.DotProductSimilarity, StorageName = "test_vector" }; // Act var (vectorSearchField, algorithmConfiguration, vectorSearchProfile) = AzureAISearchCollectionCreateMapping.MapVectorField(vectorProperty); // Assert Assert.Equal("test_vectorAlgoConfig", algorithmConfiguration.Name); Assert.Equal(algoConfigType, algorithmConfiguration.GetType()); } [Fact] public void MapVectorFieldDefaultsToHsnwAndCosine() { // Arrange var vectorProperty = new VectorPropertyModel("testvector", typeof(ReadOnlyMemory)) { Dimensions = 10 }; // Act var (vectorSearchField, algorithmConfiguration, vectorSearchProfile) = AzureAISearchCollectionCreateMapping.MapVectorField(vectorProperty); // Assert Assert.IsType(algorithmConfiguration); var hnswConfig = algorithmConfiguration as HnswAlgorithmConfiguration; Assert.Equal(VectorSearchAlgorithmMetric.Cosine, hnswConfig!.Parameters.Metric); } [Fact] public void MapVectorFieldThrowsForUnsupportedDistanceFunction() { // Arrange var vectorProperty = new VectorPropertyModel("testvector", typeof(ReadOnlyMemory)) { Dimensions = 10, DistanceFunction = DistanceFunction.ManhattanDistance, }; // Act & Assert Assert.Throws(() => AzureAISearchCollectionCreateMapping.MapVectorField(vectorProperty)); } [Theory] [MemberData(nameof(DataTypeMappingOptions))] public void GetSDKFieldDataTypeMapsTypesCorrectly(Type propertyType, SearchFieldDataType searchFieldDataType) { // Act & Assert Assert.Equal(searchFieldDataType, AzureAISearchCollectionCreateMapping.GetSDKFieldDataType(propertyType)); } public static IEnumerable DataTypeMappingOptions() { yield return new object[] { typeof(string), SearchFieldDataType.String }; yield return new object[] { typeof(bool), SearchFieldDataType.Boolean }; yield return new object[] { typeof(int), SearchFieldDataType.Int32 }; yield return new object[] { typeof(long), SearchFieldDataType.Int64 }; yield return new object[] { typeof(float), SearchFieldDataType.Double }; yield return new object[] { typeof(double), SearchFieldDataType.Double }; yield return new object[] { typeof(DateTimeOffset), SearchFieldDataType.DateTimeOffset }; yield return new object[] { typeof(string[]), SearchFieldDataType.Collection(SearchFieldDataType.String) }; yield return new object[] { typeof(List), SearchFieldDataType.Collection(SearchFieldDataType.String) }; } }