using AyCode.Core.Extensions; using AyCode.Core.Tests.TestModels; namespace AyCode.Core.Tests.serialization; [TestClass] public class AcBinarySerializerTests { #region Basic Serialization Tests [TestMethod] public void Serialize_Null_ReturnsSingleNullByte() { var result = AcBinarySerializer.Serialize(null); Assert.AreEqual(1, result.Length); Assert.AreEqual((byte)0, result[0]); // BinaryTypeCode.Null = 0 } [TestMethod] public void Serialize_Int32_RoundTrip() { var value = 12345; var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] public void Serialize_Int64_RoundTrip() { var value = 123456789012345L; var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] public void Serialize_Double_RoundTrip() { var value = 3.14159265358979; var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] public void Serialize_String_RoundTrip() { var value = "Hello, Binary World!"; var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] public void Serialize_Boolean_RoundTrip() { var trueResult = AcBinaryDeserializer.Deserialize(AcBinarySerializer.Serialize(true)); var falseResult = AcBinaryDeserializer.Deserialize(AcBinarySerializer.Serialize(false)); Assert.IsTrue(trueResult); Assert.IsFalse(falseResult); } [TestMethod] public void Serialize_DateTime_RoundTrip() { var value = new DateTime(2024, 12, 25, 10, 30, 45, DateTimeKind.Utc); var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] [DataRow(DateTimeKind.Unspecified)] [DataRow(DateTimeKind.Utc)] [DataRow(DateTimeKind.Local)] public void Serialize_DateTime_PreservesKind(DateTimeKind kind) { var value = new DateTime(2024, 12, 25, 10, 30, 45, kind); var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value.Ticks, result.Ticks); Assert.AreEqual(value.Kind, result.Kind); } [TestMethod] public void Serialize_Guid_RoundTrip() { var value = Guid.NewGuid(); var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] public void Serialize_Decimal_RoundTrip() { var value = 123456.789012m; var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] public void Serialize_TimeSpan_RoundTrip() { var value = TimeSpan.FromHours(2.5); var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); Assert.AreEqual(value, result); } [TestMethod] public void Serialize_DateTimeOffset_RoundTrip() { var value = new DateTimeOffset(2024, 12, 25, 10, 30, 45, TimeSpan.FromHours(2)); var binary = AcBinarySerializer.Serialize(value); var result = AcBinaryDeserializer.Deserialize(binary); // Compare UTC ticks and offset separately since we store UTC ticks Assert.AreEqual(value.UtcTicks, result.UtcTicks); Assert.AreEqual(value.Offset, result.Offset); } #endregion #region Object Serialization Tests [TestMethod] public void Serialize_SimpleObject_RoundTrip() { var obj = new TestSimpleClass { Id = 42, Name = "Test Object", Value = 3.14, IsActive = true }; var binary = obj.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(obj.Id, result.Id); Assert.AreEqual(obj.Name, result.Name); Assert.AreEqual(obj.Value, result.Value); Assert.AreEqual(obj.IsActive, result.IsActive); } [TestMethod] public void Serialize_NestedObject_RoundTrip() { var obj = new TestNestedClass { Id = 1, Name = "Parent", Child = new TestSimpleClass { Id = 2, Name = "Child", Value = 2.5, IsActive = true } }; var binary = obj.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(obj.Id, result.Id); Assert.AreEqual(obj.Name, result.Name); Assert.IsNotNull(result.Child); Assert.AreEqual(obj.Child.Id, result.Child.Id); Assert.AreEqual(obj.Child.Name, result.Child.Name); } [TestMethod] public void Serialize_List_RoundTrip() { var list = new List { 1, 2, 3, 4, 5 }; var binary = list.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); CollectionAssert.AreEqual(list, result); } [TestMethod] public void Serialize_ObjectWithList_RoundTrip() { var obj = new TestClassWithList { Id = 1, Items = new List { "Item1", "Item2", "Item3" } }; var binary = obj.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(obj.Id, result.Id); Assert.IsNotNull(result.Items); CollectionAssert.AreEqual(obj.Items, result.Items); } [TestMethod] public void Serialize_Dictionary_RoundTrip() { var dict = new Dictionary { ["one"] = 1, ["two"] = 2, ["three"] = 3 }; var binary = dict.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(dict.Count, result.Count); foreach (var kvp in dict) { Assert.IsTrue(result.ContainsKey(kvp.Key)); Assert.AreEqual(kvp.Value, result[kvp.Key]); } } #endregion #region Populate Tests [TestMethod] public void Populate_UpdatesExistingObject() { var target = new TestSimpleClass { Id = 0, Name = "Original" }; var source = new TestSimpleClass { Id = 42, Name = "Updated", Value = 3.14 }; var binary = source.ToBinary(); binary.BinaryTo(target); Assert.AreEqual(42, target.Id); Assert.AreEqual("Updated", target.Name); Assert.AreEqual(3.14, target.Value); } [TestMethod] public void PopulateMerge_MergesNestedObjects() { var target = new TestNestedClass { Id = 1, Name = "Original", Child = new TestSimpleClass { Id = 10, Name = "OriginalChild", Value = 1.0 } }; var source = new TestNestedClass { Id = 2, Name = "Updated", Child = new TestSimpleClass { Id = 20, Name = "UpdatedChild", Value = 2.0 } }; var binary = source.ToBinary(); binary.BinaryToMerge(target); Assert.AreEqual(2, target.Id); Assert.AreEqual("Updated", target.Name); Assert.IsNotNull(target.Child); // Child object should be merged, not replaced Assert.AreEqual(20, target.Child.Id); Assert.AreEqual("UpdatedChild", target.Child.Name); } #endregion #region String Interning Tests [TestMethod] public void Serialize_RepeatedStrings_UsesInterning() { var obj = new TestClassWithRepeatedStrings { Field1 = "Repeated", Field2 = "Repeated", Field3 = "Repeated", Field4 = "Unique" }; var binaryWithInterning = AcBinarySerializer.Serialize(obj, AcBinarySerializerOptions.Default); var binaryWithoutInterning = AcBinarySerializer.Serialize(obj, new AcBinarySerializerOptions { UseStringInterning = false }); // With interning should be smaller Assert.IsTrue(binaryWithInterning.Length < binaryWithoutInterning.Length, $"With interning: {binaryWithInterning.Length}, Without: {binaryWithoutInterning.Length}"); // Both should deserialize correctly var result1 = AcBinaryDeserializer.Deserialize(binaryWithInterning); var result2 = AcBinaryDeserializer.Deserialize(binaryWithoutInterning); Assert.AreEqual(obj.Field1, result1!.Field1); Assert.AreEqual(obj.Field1, result2!.Field1); } #endregion #region Size Comparison Tests [TestMethod] public void Serialize_IsSmallerThanJson() { var obj = TestDataFactory.CreateBenchmarkOrder(itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 3); var jsonBytes = System.Text.Encoding.UTF8.GetBytes(obj.ToJson()); var binaryBytes = obj.ToBinary(); Console.WriteLine($"JSON size: {jsonBytes.Length} bytes"); Console.WriteLine($"Binary size: {binaryBytes.Length} bytes"); Console.WriteLine($"Ratio: {(double)binaryBytes.Length / jsonBytes.Length:P2}"); Assert.IsTrue(binaryBytes.Length < jsonBytes.Length, $"Binary ({binaryBytes.Length}) should be smaller than JSON ({jsonBytes.Length})"); // Verify roundtrip works var result = binaryBytes.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual(obj.Id, result.Id); Assert.AreEqual(obj.Items.Count, result.Items.Count); } #endregion #region Extension Method Tests [TestMethod] public void BinaryCloneTo_CreatesDeepCopy() { var original = new TestNestedClass { Id = 1, Name = "Original", Child = new TestSimpleClass { Id = 2, Name = "Child" } }; var clone = original.BinaryCloneTo(); Assert.IsNotNull(clone); Assert.AreNotSame(original, clone); Assert.AreNotSame(original.Child, clone.Child); Assert.AreEqual(original.Id, clone.Id); Assert.AreEqual(original.Child.Id, clone.Child!.Id); // Modify clone, original should be unchanged clone.Id = 999; clone.Child.Id = 888; Assert.AreEqual(1, original.Id); Assert.AreEqual(2, original.Child.Id); } #endregion #region Test Models private class TestSimpleClass { public int Id { get; set; } public string Name { get; set; } = ""; public double Value { get; set; } public bool IsActive { get; set; } } private class TestNestedClass { public int Id { get; set; } public string Name { get; set; } = ""; public TestSimpleClass? Child { get; set; } } private class TestClassWithList { public int Id { get; set; } public List Items { get; set; } = new(); } private class TestClassWithRepeatedStrings { public string Field1 { get; set; } = ""; public string Field2 { get; set; } = ""; public string Field3 { get; set; } = ""; public string Field4 { get; set; } = ""; } #endregion #region Benchmark Order Tests [TestMethod] public void Serialize_BenchmarkOrder_RoundTrip() { // This is the exact same data that causes stack overflow in benchmarks var order = TestDataFactory.CreateBenchmarkOrder( itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 5); // Should not throw stack overflow var binary = AcBinarySerializer.Serialize(order); Assert.IsTrue(binary.Length > 0, "Binary data should not be empty"); var result = AcBinaryDeserializer.Deserialize(binary); Assert.IsNotNull(result); Assert.AreEqual(order.Id, result.Id); Assert.AreEqual(order.OrderNumber, result.OrderNumber); Assert.AreEqual(order.Items.Count, result.Items.Count); } [TestMethod] public void Serialize_BenchmarkOrder_SmallData_RoundTrip() { // Smaller test to isolate the issue var order = TestDataFactory.CreateBenchmarkOrder( itemCount: 1, palletsPerItem: 1, measurementsPerPallet: 1, pointsPerMeasurement: 1); var binary = AcBinarySerializer.Serialize(order); Assert.IsTrue(binary.Length > 0); var result = AcBinaryDeserializer.Deserialize(binary); Assert.IsNotNull(result); Assert.AreEqual(order.Id, result.Id); } #endregion }