// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Extensions.VectorData; using Microsoft.Extensions.VectorData.ProviderServices; using Xunit; namespace Microsoft.SemanticKernel.Connectors.Redis.UnitTests; /// /// Contains tests for the class. /// public class RedisJsonDynamicMapperTests { private static readonly float[] s_floatVector = new float[] { 1.0f, 2.0f, 3.0f, 4.0f }; private static readonly CollectionModel s_model = new RedisJsonModelBuilder(RedisJsonCollection>.ModelBuildingOptions) .BuildDynamic( new() { Properties = [ new VectorStoreKeyProperty("Key", typeof(string)), new VectorStoreDataProperty("StringData", typeof(string)), new VectorStoreDataProperty("IntData", typeof(int)), new VectorStoreDataProperty("NullableIntData", typeof(int?)), new VectorStoreDataProperty("ComplexObjectData", typeof(ComplexObject)), new VectorStoreVectorProperty("FloatVector", typeof(ReadOnlyMemory), 10), ] }, defaultEmbeddingGenerator: null); [Fact] public void MapFromDataToStorageModelMapsAllSupportedTypes() { // Arrange. var sut = new RedisJsonDynamicMapper(s_model, JsonSerializerOptions.Default); var dataModel = new Dictionary { ["Key"] = "key", ["StringData"] = "data 1", ["IntData"] = 1, ["NullableIntData"] = 2, ["ComplexObjectData"] = new ComplexObject { Prop1 = "prop 1", Prop2 = "prop 2" }, ["FloatVector"] = new ReadOnlyMemory(s_floatVector) }; // Act. var storageModel = sut.MapFromDataToStorageModel(dataModel, recordIndex: 0, generatedEmbeddings: null); // Assert Assert.Equal("key", storageModel.Key); Assert.Equal("data 1", (string)storageModel.Node["StringData"]!); Assert.Equal(1, (int)storageModel.Node["IntData"]!); Assert.Equal(2, (int?)storageModel.Node["NullableIntData"]!); Assert.Equal("prop 1", (string)storageModel.Node["ComplexObjectData"]!.AsObject()["Prop1"]!); Assert.Equal(new float[] { 1, 2, 3, 4 }, storageModel.Node["FloatVector"]?.AsArray().GetValues().ToArray()); } [Fact] public void MapFromDataToStorageModelMapsNullValues() { // Arrange. var sut = new RedisJsonDynamicMapper(s_model, JsonSerializerOptions.Default); var dataModel = new Dictionary { ["Key"] = "key", ["StringData"] = null, ["IntData"] = null, ["NullableIntData"] = null, ["ComplexObjectData"] = null, ["FloatVector"] = null, }; // Act. var storageModel = sut.MapFromDataToStorageModel(dataModel, recordIndex: 0, generatedEmbeddings: null); // Assert Assert.Equal("key", storageModel.Key); Assert.Null(storageModel.Node["storage_string_data"]); Assert.Null(storageModel.Node["IntData"]); Assert.Null(storageModel.Node["NullableIntData"]); Assert.Null(storageModel.Node["ComplexObjectData"]); Assert.Null(storageModel.Node["FloatVector"]); } [Fact] public void MapFromStorageToDataModelMapsAllSupportedTypes() { // Arrange. var sut = new RedisJsonDynamicMapper(s_model, JsonSerializerOptions.Default); var storageModel = new JsonObject { { "StringData", "data 1" }, { "IntData", 1 }, { "NullableIntData", 2 }, { "ComplexObjectData", new JsonObject(new KeyValuePair[] { new("Prop1", JsonValue.Create("prop 1")), new("Prop2", JsonValue.Create("prop 2")) }) }, { "FloatVector", new JsonArray(new float[] { 1, 2, 3, 4 }.Select(x => JsonValue.Create(x)).ToArray()) } }; // Act. var dataModel = sut.MapFromStorageToDataModel(("key", storageModel), includeVectors: true); // Assert. Assert.Equal("key", dataModel["Key"]); Assert.Equal("data 1", dataModel["StringData"]); Assert.Equal(1, dataModel["IntData"]); Assert.Equal(2, dataModel["NullableIntData"]); Assert.Equal("prop 1", ((ComplexObject)dataModel["ComplexObjectData"]!).Prop1); Assert.Equal(new float[] { 1, 2, 3, 4 }, ((ReadOnlyMemory)dataModel["FloatVector"]!).ToArray()); } [Fact] public void MapFromStorageToDataModelMapsNullValues() { // Arrange. var sut = new RedisJsonDynamicMapper(s_model, JsonSerializerOptions.Default); var storageModel = new JsonObject { { "StringData", null }, { "IntData", null }, { "NullableIntData", null }, { "ComplexObjectData", null }, { "FloatVector", null } }; // Act. var dataModel = sut.MapFromStorageToDataModel(("key", storageModel), includeVectors: true); // Assert. Assert.Equal("key", dataModel["Key"]); Assert.Null(dataModel["StringData"]); Assert.Null(dataModel["IntData"]); Assert.Null(dataModel["NullableIntData"]); Assert.Null(dataModel["ComplexObjectData"]); Assert.Null(dataModel["FloatVector"]); } [Fact] public void MapFromDataToStorageModelSkipsMissingProperties() { // Arrange. var sut = new RedisJsonDynamicMapper(s_model, JsonSerializerOptions.Default); var dataModel = new Dictionary { ["Key"] = "key" }; // Act. var storageModel = sut.MapFromDataToStorageModel(dataModel, recordIndex: 0, generatedEmbeddings: null); // Assert Assert.Equal("key", storageModel.Key); Assert.Empty(storageModel.Node.AsObject()); } [Fact] public void MapFromStorageToDataModelSkipsMissingProperties() { // Arrange. var storageModel = new JsonObject(); var sut = new RedisJsonDynamicMapper(s_model, JsonSerializerOptions.Default); // Act. var dataModel = sut.MapFromStorageToDataModel(("key", storageModel), includeVectors: true); // Assert. Assert.Equal("key", dataModel["Key"]); Assert.Single(dataModel); } private sealed class ComplexObject { public string Prop1 { get; set; } = string.Empty; public string Prop2 { get; set; } = string.Empty; } }