using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Expressions; using AyCode.Core.Serializers.Jsons; using AyCode.Core.Tests.TestModels; namespace AyCode.Core.Tests.Serialization; /// /// Tests for AcExpressionNode serialization. /// These tests verify that the serializers properly handle expression nodes /// with the new type-safe constant value storage. /// [TestClass] public class AcExpressionNodeSerializationTests { #region JSON Serializer Tests /// /// Tests that AcExpressionNode with type-safe properties can be JSON serialized. /// [TestMethod] public void AcJsonSerializer_WithAcExpressionNode_RoundTrip_Works() { // Arrange - Create an expression with a constant value System.Linq.Expressions.Expression> filterExpression = item => item.Quantity > 5; var expressionNode = AcExpressionConverter.ToNode(filterExpression); // Act - Serialize var json = AcJsonSerializer.Serialize(expressionNode); Console.WriteLine($"Serialized JSON: {json}"); Assert.IsNotNull(json, "JSON should not be null"); Assert.IsTrue(json.Length > 0, "JSON should not be empty"); // Deserialize var deserialized = AcJsonDeserializer.Deserialize(json); Assert.IsNotNull(deserialized, "Deserialized node should not be null"); // Rebuild and test var rebuiltExpression = AcExpressionRebuilder.FromNode(deserialized); var compiled = rebuiltExpression.Compile(); var matchingItem = new TestOrderItem { Id = 1, Quantity = 10 }; var nonMatchingItem = new TestOrderItem { Id = 2, Quantity = 3 }; Assert.IsTrue(compiled(matchingItem), "Matching item should pass filter"); Assert.IsFalse(compiled(nonMatchingItem), "Non-matching item should fail filter"); } /// /// Tests that decimal constant values are preserved after JSON round-trip. /// [TestMethod] public void AcJsonSerializer_WithDecimalValue_PreservesType() { // Arrange - Node with decimal constant var node = new AcExpressionNode { NodeType = System.Linq.Expressions.ExpressionType.Constant, TypeName = "System.Decimal" }; node.SetValue(99.99m); // Act - JSON round-trip var json = AcJsonSerializer.Serialize(node); Console.WriteLine($"JSON: {json}"); var deserialized = AcJsonDeserializer.Deserialize(json); // Assert Assert.IsNotNull(deserialized); Assert.AreEqual(ConstantValueType.Decimal, deserialized.ValueType); var value = deserialized.GetValue(); Assert.IsNotNull(value, "Value should not be null"); Assert.AreEqual(typeof(decimal), value.GetType(), "Should preserve decimal type"); Assert.AreEqual(99.99m, (decimal)value); } /// /// Tests that Guid constant values are preserved after JSON round-trip. /// [TestMethod] public void AcJsonSerializer_WithGuidValue_PreservesType() { // Arrange var testGuid = Guid.NewGuid(); var node = new AcExpressionNode { NodeType = System.Linq.Expressions.ExpressionType.Constant, TypeName = "System.Guid" }; node.SetValue(testGuid); // Act var json = AcJsonSerializer.Serialize(node); Console.WriteLine($"JSON: {json}"); var deserialized = AcJsonDeserializer.Deserialize(json); // Assert Assert.IsNotNull(deserialized); Assert.AreEqual(ConstantValueType.Guid, deserialized.ValueType); var value = deserialized.GetValue(); Assert.IsNotNull(value); Assert.AreEqual(typeof(Guid), value.GetType(), "Should preserve Guid type"); Assert.AreEqual(testGuid, (Guid)value); } #endregion #region Binary Serializer Tests /// /// Tests AcBinarySerializer with AcExpressionNode round-trip. /// [TestMethod] public void AcBinarySerializer_WithAcExpressionNode_RoundTrip_Works() { // Arrange System.Linq.Expressions.Expression> filterExpression = item => item.Quantity > 5; var originalNode = AcExpressionConverter.ToNode(filterExpression); // Act - Binary round-trip var bytes = AcBinarySerializer.Serialize(originalNode); Console.WriteLine($"Binary size: {bytes.Length} bytes"); var deserialized = AcBinaryDeserializer.Deserialize(bytes); // Assert Assert.IsNotNull(deserialized); Assert.AreEqual(originalNode.NodeType, deserialized.NodeType); // Rebuild and test var rebuiltExpression = AcExpressionRebuilder.FromNode(deserialized); var compiled = rebuiltExpression.Compile(); var matchingItem = new TestOrderItem { Id = 1, Quantity = 10 }; var nonMatchingItem = new TestOrderItem { Id = 2, Quantity = 3 }; Assert.IsTrue(compiled(matchingItem), "Matching item should pass filter"); Assert.IsFalse(compiled(nonMatchingItem), "Non-matching item should fail filter"); } /// /// Tests AcBinarySerializer with decimal in typed property. /// [TestMethod] public void AcBinarySerializer_WithDecimalValue_PreservesType() { // Arrange var node = new AcExpressionNode { NodeType = System.Linq.Expressions.ExpressionType.Constant, TypeName = "System.Decimal" }; node.SetValue(99.99m); // Act var bytes = AcBinarySerializer.Serialize(node); var deserialized = AcBinaryDeserializer.Deserialize(bytes); // Assert Assert.IsNotNull(deserialized); Assert.AreEqual(ConstantValueType.Decimal, deserialized.ValueType); var value = deserialized.GetValue(); Assert.IsNotNull(value); Assert.AreEqual(typeof(decimal), value.GetType(), "Binary should preserve decimal type"); Assert.AreEqual(99.99m, (decimal)value); } /// /// Tests complex expression with decimal comparison. /// [TestMethod] public void AcBinarySerializer_WithDecimalComparison_RoundTrip_Works() { // Arrange - Expression with captured decimal: item => item.UnitPrice > 99.99m var minPrice = 99.99m; System.Linq.Expressions.Expression> filterExpression = item => item.UnitPrice > minPrice; var originalNode = AcExpressionConverter.ToNode(filterExpression); // Act - Binary round-trip var bytes = AcBinarySerializer.Serialize(originalNode); var deserializedNode = AcBinaryDeserializer.Deserialize(bytes); // Assert - Rebuild and verify it still works with decimal comparison Assert.IsNotNull(deserializedNode); var rebuiltExpression = AcExpressionRebuilder.FromNode(deserializedNode); var compiledFilter = rebuiltExpression.Compile(); var expensiveItem = new TestOrderItem { UnitPrice = 150m }; var cheapItem = new TestOrderItem { UnitPrice = 50m }; Assert.IsTrue(compiledFilter(expensiveItem), "Expensive item should pass filter"); Assert.IsFalse(compiledFilter(cheapItem), "Cheap item should fail filter"); } /// /// Tests enum constant values. /// [TestMethod] public void AcBinarySerializer_WithEnumValue_PreservesType() { // Arrange - Expression with enum comparison System.Linq.Expressions.Expression> filterExpression = item => item.Status == TestStatus.Completed; var originalNode = AcExpressionConverter.ToNode(filterExpression); // Act - Binary round-trip var bytes = AcBinarySerializer.Serialize(originalNode); var deserializedNode = AcBinaryDeserializer.Deserialize(bytes); // Assert Assert.IsNotNull(deserializedNode); var rebuiltExpression = AcExpressionRebuilder.FromNode(deserializedNode); var compiledFilter = rebuiltExpression.Compile(); var completedItem = new TestOrderItem { Status = TestStatus.Completed }; var pendingItem = new TestOrderItem { Status = TestStatus.Pending }; Assert.IsTrue(compiledFilter(completedItem), "Completed item should pass filter"); Assert.IsFalse(compiledFilter(pendingItem), "Pending item should fail filter"); } #endregion #region SetValue/GetDynamicValue Tests [TestMethod] [DataRow(42, ConstantValueType.Int32)] [DataRow(long.MaxValue, ConstantValueType.Int64)] [DataRow(3.14, ConstantValueType.Double)] [DataRow(true, ConstantValueType.Boolean)] [DataRow("test", ConstantValueType.String)] public void SetValue_GetValue_RoundTrip_PreservesValue(object value, ConstantValueType expectedType) { // Arrange var node = new AcExpressionNode(); // Act node.SetValue(value); var result = node.GetValue(); // Assert Assert.AreEqual(expectedType, node.ValueType); Assert.AreEqual(value, result); } [TestMethod] public void SetValue_Null_SetsNullType() { var node = new AcExpressionNode(); node.SetValue(null); Assert.AreEqual(ConstantValueType.Null, node.ValueType); Assert.IsNull(node.GetValue()); } [TestMethod] public void SetValue_Guid_PreservesValue() { var guid = Guid.NewGuid(); var node = new AcExpressionNode(); node.SetValue(guid); Assert.AreEqual(ConstantValueType.Guid, node.ValueType); Assert.AreEqual(guid, node.GetValue()); } [TestMethod] public void SetValue_DateTime_PreservesValue() { var dateTime = new DateTime(2024, 6, 15, 10, 30, 45, DateTimeKind.Utc); var node = new AcExpressionNode(); node.SetValue(dateTime); Assert.AreEqual(ConstantValueType.DateTime, node.ValueType); Assert.AreEqual(dateTime, node.GetValue()); } [TestMethod] public void SetValue_Decimal_PreservesValue() { var value = 123.456789m; var node = new AcExpressionNode(); node.SetValue(value); Assert.AreEqual(ConstantValueType.Decimal, node.ValueType); Assert.AreEqual(value, node.GetValue()); } [TestMethod] public void SetValue_Enum_PreservesValue() { var node = new AcExpressionNode(); node.SetValue(TestStatus.Processing); Assert.AreEqual(ConstantValueType.Enum, node.ValueType); Assert.IsTrue(node.EnumTypeName!.StartsWith("AyCode.Core.Tests.TestModels.TestStatus"), $"EnumTypeName should start with full type name, got: {node.EnumTypeName}"); Assert.AreEqual(TestStatus.Processing, node.GetValue()); } #endregion }