using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Binaries;
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
}