using AyCode.Core.Extensions; namespace AyCode.Core.Tests.Serialization; /// /// Tests for DateTime serialization in Binary format. /// Covers edge cases like string-stored DateTime values in GenericAttribute-like scenarios. /// [TestClass] public class AcBinaryDateTimeSerializationTests { #region DateTime Direct Serialization [TestMethod] public void DateTime_RoundTrip_PreservesValue() { var original = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc); var binary = AcBinarySerializer.Serialize(original); var result = binary.BinaryTo(); Assert.AreEqual(original, result); Assert.AreEqual(DateTimeKind.Utc, result.Kind); } [TestMethod] public void NullableDateTime_RoundTrip_PreservesValue() { DateTime? original = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc); var binary = AcBinarySerializer.Serialize(original); var result = binary.BinaryTo(); Assert.AreEqual(original, result); } [TestMethod] public void NullableDateTime_Null_RoundTrip_PreservesNull() { DateTime? original = null; var binary = AcBinarySerializer.Serialize(original); var result = binary.BinaryTo(); Assert.IsNull(result); } #endregion #region Object with DateTime Property [TestMethod] public void ObjectWithDateTime_RoundTrip_PreservesValue() { var original = new TestObjectWithDateTime { Id = 1, CreatedAt = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc), UpdatedAt = new DateTime(2025, 12, 31, 23, 59, 59, DateTimeKind.Utc) }; var binary = original.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(original.Id, result.Id); Assert.AreEqual(original.CreatedAt, result.CreatedAt); Assert.AreEqual(original.UpdatedAt, result.UpdatedAt); } [TestMethod] public void ObjectWithNullableDateTime_Null_RoundTrip_PreservesNull() { var original = new TestObjectWithNullableDateTime { Id = 1, DateOfReceipt = null }; var binary = original.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(original.Id, result.Id); Assert.IsNull(result.DateOfReceipt); } [TestMethod] public void ObjectWithNullableDateTime_HasValue_RoundTrip_PreservesValue() { var original = new TestObjectWithNullableDateTime { Id = 1, DateOfReceipt = new DateTime(2025, 10, 24, 0, 27, 0, DateTimeKind.Utc) }; var binary = original.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(original.Id, result.Id); Assert.AreEqual(original.DateOfReceipt, result.DateOfReceipt); } #endregion #region GenericAttribute-like Scenario (String stored DateTime) [TestMethod] public void GenericAttributeScenario_DateTimeAsString_PreservesValue() { var original = new TestGenericAttribute { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" }; var binary = original.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(original.Key, result.Key); Assert.AreEqual(original.Value, result.Value); } [TestMethod] public void GenericAttributeScenario_ZeroValue_PreservesValue() { var original = new TestGenericAttribute { Key = "DateOfReceipt", Value = "0" }; var binary = original.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(original.Key, result.Key); Assert.AreEqual("0", result.Value); } [TestMethod] public void GenericAttributeList_RoundTrip_PreservesAllValues() { var original = new List { new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" }, new() { Key = "SomeNumber", Value = "42" }, new() { Key = "EmptyValue", Value = "" }, new() { Key = "ZeroValue", Value = "0" } }; var binary = original.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(4, result.Count); Assert.AreEqual("DateOfReceipt", result[0].Key); Assert.AreEqual("10/24/2025 00:27:00", result[0].Value); Assert.AreEqual("SomeNumber", result[1].Key); Assert.AreEqual("42", result[1].Value); Assert.AreEqual("EmptyValue", result[2].Key); Assert.AreEqual("", result[2].Value); Assert.AreEqual("ZeroValue", result[3].Key); Assert.AreEqual("0", result[3].Value); } [TestMethod] public void ObjectWithGenericAttributes_RoundTrip_PreservesAllValues() { var original = new TestDtoWithGenericAttributes { Id = 123, Name = "Test Order", GenericAttributes = [ new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" }, new() { Key = "Priority", Value = "1" } ] }; var binary = original.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(123, result.Id); Assert.AreEqual("Test Order", result.Name); Assert.AreEqual(2, result.GenericAttributes.Count); var dateAttr = result.GenericAttributes.FirstOrDefault(x => x.Key == "DateOfReceipt"); Assert.IsNotNull(dateAttr); Assert.AreEqual("10/24/2025 00:27:00", dateAttr.Value); } #endregion #region JSON vs Binary Comparison [TestMethod] public void GenericAttribute_JsonAndBinary_ProduceSameResult() { var original = new TestGenericAttribute { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" }; var json = original.ToJson(); var jsonResult = json.JsonTo(); var binary = original.ToBinary(); var binaryResult = binary.BinaryTo(); Assert.IsNotNull(jsonResult); Assert.IsNotNull(binaryResult); Assert.AreEqual(jsonResult.Key, binaryResult.Key); Assert.AreEqual(jsonResult.Value, binaryResult.Value); } [TestMethod] public void DtoWithGenericAttributes_JsonAndBinary_ProduceSameResult() { var original = new TestDtoWithGenericAttributes { Id = 123, Name = "Test Order", GenericAttributes = [ new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" }, new() { Key = "ZeroValue", Value = "0" } ] }; var json = original.ToJson(); var jsonResult = json.JsonTo(); var binary = original.ToBinary(); var binaryResult = binary.BinaryTo(); Assert.IsNotNull(jsonResult); Assert.IsNotNull(binaryResult); Assert.AreEqual(jsonResult.Id, binaryResult.Id); Assert.AreEqual(jsonResult.Name, binaryResult.Name); Assert.AreEqual(jsonResult.GenericAttributes.Count, binaryResult.GenericAttributes.Count); for (int i = 0; i < jsonResult.GenericAttributes.Count; i++) { Assert.AreEqual(jsonResult.GenericAttributes[i].Key, binaryResult.GenericAttributes[i].Key); Assert.AreEqual(jsonResult.GenericAttributes[i].Value, binaryResult.GenericAttributes[i].Value); } } #endregion #region Test Models public class TestObjectWithDateTime { public int Id { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } } public class TestObjectWithNullableDateTime { public int Id { get; set; } public DateTime? DateOfReceipt { get; set; } } public class TestGenericAttribute { public string Key { get; set; } = ""; public string Value { get; set; } = ""; } public class TestDtoWithGenericAttributes { public int Id { get; set; } public string Name { get; set; } = ""; public List GenericAttributes { get; set; } = []; } #endregion #region Exact Production Scenario Test /// /// This test reproduces the exact production bug scenario: /// 1. Server sends Binary serialized response with GenericAttributes /// 2. Client deserializes the Binary response /// 3. Client accesses DateOfReceipt property which reads from GenericAttributes /// 4. CommonHelper2.To fails to parse the string value /// [TestMethod] public void ProductionScenario_GenericAttributeWithDateString_PreservesExactFormat() { // Arrange: Create DTO with GenericAttributes like in production var original = new TestDtoWithGenericAttributes { Id = 123, Name = "Test Order", GenericAttributes = [ new() { Key = "DateOfReceipt", Value = "10/24/2025 00:27:00" }, new() { Key = "Priority", Value = "1" }, new() { Key = "SomeFlag", Value = "true" } ] }; // Act: Binary round-trip (simulates server->client communication) var binary = original.ToBinary(); var result = binary.BinaryTo(); // Assert: The exact string value must be preserved Assert.IsNotNull(result); var dateAttr = result.GenericAttributes.FirstOrDefault(x => x.Key == "DateOfReceipt"); Assert.IsNotNull(dateAttr, "DateOfReceipt attribute should exist"); // This is the critical assertion - the EXACT string must be preserved Assert.AreEqual("10/24/2025 00:27:00", dateAttr.Value, $"Expected '10/24/2025 00:27:00' but got '{dateAttr.Value}'"); // Verify it can be parsed with US culture (which is how it was stored) Assert.IsTrue(DateTime.TryParse(dateAttr.Value, new System.Globalization.CultureInfo("en-US"), System.Globalization.DateTimeStyles.None, out var parsedDate), $"Value '{dateAttr.Value}' should be parseable as US date format"); Assert.AreEqual(new DateTime(2025, 10, 24, 0, 27, 0), parsedDate); } /// /// Test that verifies the exact bytes of the string are preserved. /// [TestMethod] public void ProductionScenario_StringWithSlashes_BytesArePreserved() { var original = "10/24/2025 00:27:00"; var originalBytes = System.Text.Encoding.UTF8.GetBytes(original); // Serialize and deserialize var binary = AcBinarySerializer.Serialize(original); var result = binary.BinaryTo(); Assert.IsNotNull(result); var resultBytes = System.Text.Encoding.UTF8.GetBytes(result); // Compare byte-by-byte Assert.AreEqual(originalBytes.Length, resultBytes.Length, "String length changed after serialization"); for (int i = 0; i < originalBytes.Length; i++) { Assert.AreEqual(originalBytes[i], resultBytes[i], $"Byte at position {i} differs: expected {originalBytes[i]:X2} ('{(char)originalBytes[i]}'), got {resultBytes[i]:X2} ('{(char)resultBytes[i]}')"); } } /// /// Test with large list of GenericAttributes to catch any edge cases. /// [TestMethod] public void ProductionScenario_ManyGenericAttributes_AllPreserved() { var original = new TestDtoWithGenericAttributes { Id = 999, Name = "Large Order", GenericAttributes = Enumerable.Range(0, 50).Select(i => new TestGenericAttribute { Key = $"Attribute_{i}", Value = i % 5 == 0 ? $"{i}/24/2025 00:00:00" : i.ToString() }).ToList() }; var binary = original.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(50, result.GenericAttributes.Count); for (int i = 0; i < 50; i++) { var expectedValue = i % 5 == 0 ? $"{i}/24/2025 00:00:00" : i.ToString(); Assert.AreEqual($"Attribute_{i}", result.GenericAttributes[i].Key); Assert.AreEqual(expectedValue, result.GenericAttributes[i].Value, $"Attribute_{i} value mismatch: expected '{expectedValue}', got '{result.GenericAttributes[i].Value}'"); } } #endregion #region CommonHelper2.To Simulation Tests /// /// This test simulates what CommonHelper2.To does with various string values. /// It helps identify which values will cause the FormatException. /// [TestMethod] public void CommonHelperSimulation_ValidDateString_ParsesSuccessfully() { var dateString = "10/24/2025 00:27:00"; // This is what CommonHelper2.To does internally var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(DateTime)); Assert.IsTrue(converter.CanConvertFrom(typeof(string))); // With InvariantCulture var result = converter.ConvertFrom(null, System.Globalization.CultureInfo.InvariantCulture, dateString); Assert.IsNotNull(result); Assert.IsInstanceOfType(result, typeof(DateTime)); var dt = (DateTime)result; // InvariantCulture interprets 10/24/2025 as October 24, 2025 Assert.AreEqual(2025, dt.Year); Assert.AreEqual(10, dt.Month); Assert.AreEqual(24, dt.Day); } /// /// This test shows that "0" cannot be parsed as DateTime - this is the actual bug! /// [TestMethod] public void CommonHelperSimulation_ZeroString_ThrowsFormatException() { var invalidValue = "0"; var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(DateTime)); // This should throw FormatException - exactly what we see in production var threw = false; try { converter.ConvertFrom(null, System.Globalization.CultureInfo.InvariantCulture, invalidValue); } catch (FormatException) { threw = true; } Assert.IsTrue(threw, "Converting '0' to DateTime should throw FormatException"); } /// /// Test various invalid DateTime strings that might be stored in GenericAttributes. /// [TestMethod] [DataRow("0")] [DataRow("null")] [DataRow("undefined")] [DataRow("N/A")] public void CommonHelperSimulation_InvalidDateStrings_ThrowFormatException(string invalidValue) { var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(DateTime)); var threw = false; try { converter.ConvertFrom(null, System.Globalization.CultureInfo.InvariantCulture, invalidValue); } catch (FormatException) { threw = true; } Assert.IsTrue(threw, $"Converting '{invalidValue}' to DateTime should throw FormatException"); } #endregion }