// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Extensions.VectorData;
using Microsoft.Extensions.VectorData.ProviderServices;
using StackExchange.Redis;
using Xunit;
namespace Microsoft.SemanticKernel.Connectors.Redis.UnitTests;
///
/// Contains dynamic mapping tests for the class.
///
public class RedisHashSetDynamicMappingTests
{
private static readonly CollectionModel s_model = BuildModel(RedisHashSetMappingTestHelpers.s_definition);
private static readonly float[] s_floatVector = new float[] { 1.0f, 2.0f, 3.0f, 4.0f };
private static readonly double[] s_doubleVector = new double[] { 5.0d, 6.0d, 7.0d, 8.0d };
[Fact]
public void MapFromDataToStorageModelMapsAllSupportedTypes()
{
// Arrange.
var sut = new RedisHashSetMapper>(s_model);
var dataModel = new Dictionary
{
["Key"] = "key",
["StringData"] = "data 1",
["IntData"] = 1,
["UIntData"] = 2u,
["LongData"] = 3L,
["ULongData"] = 4ul,
["DoubleData"] = 5.5d,
["FloatData"] = 6.6f,
["NullableIntData"] = 7,
["NullableUIntData"] = 8u,
["NullableLongData"] = 9L,
["NullableULongData"] = 10ul,
["NullableDoubleData"] = 11.1d,
["NullableFloatData"] = 12.2f,
["FloatVector"] = new ReadOnlyMemory(s_floatVector),
["DoubleVector"] = new ReadOnlyMemory(s_doubleVector),
};
// Act.
var storageModel = sut.MapFromDataToStorageModel(dataModel, recordIndex: 0, generatedEmbeddings: null);
// Assert
Assert.Equal("key", storageModel.Key);
RedisHashSetMappingTestHelpers.VerifyHashSet(storageModel.HashEntries);
}
[Fact]
public void MapFromDataToStorageModelMapsNullValues()
{
// Arrange
CollectionModel model = BuildModel(new()
{
Properties =
[
new VectorStoreKeyProperty("Key", typeof(string)),
new VectorStoreDataProperty("StringData", typeof(string)) { StorageName = "storage_string_data" },
new VectorStoreDataProperty("NullableIntData", typeof(int?)),
new VectorStoreVectorProperty("FloatVector", typeof(ReadOnlyMemory?), 10),
],
});
var dataModel = new Dictionary
{
["Key"] = "key",
["StringData"] = null,
["NullableIntData"] = null,
["FloatVector"] = null,
};
var sut = new RedisHashSetMapper>(model);
// Act
var storageModel = sut.MapFromDataToStorageModel(dataModel, recordIndex: 0, generatedEmbeddings: null);
// Assert
Assert.Equal("key", storageModel.Key);
Assert.Equal("storage_string_data", storageModel.HashEntries[0].Name.ToString());
Assert.True(storageModel.HashEntries[0].Value.IsNull);
Assert.Equal("NullableIntData", storageModel.HashEntries[1].Name.ToString());
Assert.True(storageModel.HashEntries[1].Value.IsNull);
}
[Fact]
public void MapFromStorageToDataModelMapsAllSupportedTypes()
{
// Arrange.
var hashSet = RedisHashSetMappingTestHelpers.CreateHashSet();
var sut = new RedisHashSetMapper>(s_model);
// Act.
var dataModel = sut.MapFromStorageToDataModel(("key", hashSet), includeVectors: true);
// Assert.
Assert.Equal("key", dataModel["Key"]);
Assert.Equal("data 1", dataModel["StringData"]);
Assert.Equal(1, dataModel["IntData"]);
Assert.Equal(2u, dataModel["UIntData"]);
Assert.Equal(3L, dataModel["LongData"]);
Assert.Equal(4ul, dataModel["ULongData"]);
Assert.Equal(5.5d, dataModel["DoubleData"]);
Assert.Equal(6.6f, dataModel["FloatData"]);
Assert.Equal(7, dataModel["NullableIntData"]);
Assert.Equal(8u, dataModel["NullableUIntData"]);
Assert.Equal(9L, dataModel["NullableLongData"]);
Assert.Equal(10ul, dataModel["NullableULongData"]);
Assert.Equal(11.1d, dataModel["NullableDoubleData"]);
Assert.Equal(12.2f, dataModel["NullableFloatData"]);
Assert.Equal(new float[] { 1, 2, 3, 4 }, ((ReadOnlyMemory)dataModel["FloatVector"]!).ToArray());
Assert.Equal(new double[] { 5, 6, 7, 8 }, ((ReadOnlyMemory)dataModel["DoubleVector"]!).ToArray());
}
[Fact]
public void MapFromStorageToDataModelMapsNullValues()
{
// Arrange
var model = BuildModel(new()
{
Properties =
[
new VectorStoreKeyProperty("Key", typeof(string)),
new VectorStoreDataProperty("StringData", typeof(string)) { StorageName = "storage_string_data" },
new VectorStoreDataProperty("NullableIntData", typeof(int?)),
new VectorStoreVectorProperty("FloatVector", typeof(ReadOnlyMemory?), 10),
]
});
var hashSet = new HashEntry[]
{
new("storage_string_data", RedisValue.Null),
new("NullableIntData", RedisValue.Null),
new("FloatVector", RedisValue.Null),
};
var sut = new RedisHashSetMapper>(model);
// Act
var dataModel = sut.MapFromStorageToDataModel(("key", hashSet), includeVectors: true);
// Assert
Assert.Equal("key", dataModel["Key"]);
Assert.Null(dataModel["StringData"]);
Assert.Null(dataModel["NullableIntData"]);
Assert.Null(dataModel["FloatVector"]);
}
[Fact]
public void MapFromDataToStorageModelSkipsMissingProperties()
{
// Arrange.
var model = BuildModel(new()
{
Properties =
[
new VectorStoreKeyProperty("Key", typeof(string)),
new VectorStoreDataProperty("StringData", typeof(string)) { StorageName = "storage_string_data" },
new VectorStoreDataProperty("NullableIntData", typeof(int?)),
new VectorStoreVectorProperty("FloatVector", typeof(ReadOnlyMemory?), 10),
]
});
var sut = new RedisHashSetMapper>(model);
var dataModel = new Dictionary { ["Key"] = "key" };
// Act.
var storageModel = sut.MapFromDataToStorageModel(dataModel, recordIndex: 0, generatedEmbeddings: null);
// Assert
Assert.Equal("key", storageModel.Key);
Assert.Equal("storage_string_data", storageModel.HashEntries[0].Name.ToString());
Assert.True(storageModel.HashEntries[0].Value.IsNull);
Assert.Equal("NullableIntData", storageModel.HashEntries[1].Name.ToString());
Assert.True(storageModel.HashEntries[1].Value.IsNull);
}
[Fact]
public void MapFromStorageToDataModelSkipsMissingProperties()
{
// Arrange.
var hashSet = Array.Empty();
var sut = new RedisHashSetMapper>(s_model);
// Act.
var dataModel = sut.MapFromStorageToDataModel(("key", hashSet), includeVectors: true);
// Assert.
Assert.Single(dataModel);
Assert.Equal("key", dataModel["Key"]);
}
private static CollectionModel BuildModel(VectorStoreCollectionDefinition definition)
=> new RedisModelBuilder(RedisHashSetCollection