using AyCode.Core.Extensions; using AyCode.Core.Serializers.Binaries; using static AyCode.Core.Tests.Serialization.AcSerializerModels; using static AyCode.Core.Tests.Serialization.AcSerializerTestHelper; namespace AyCode.Core.Tests.Serialization; /// /// Tests for string interning functionality. /// [TestClass] public class AcBinarySerializerStringInterningTests { [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 }); Assert.IsTrue(binaryWithInterning.Length < binaryWithoutInterning.Length, $"With interning: {binaryWithInterning.Length}, Without: {binaryWithoutInterning.Length}"); var result1 = AcBinaryDeserializer.Deserialize(binaryWithInterning); var result2 = AcBinaryDeserializer.Deserialize(binaryWithoutInterning); Assert.AreEqual(obj.Field1, result1!.Field1); Assert.AreEqual(obj.Field1, result2!.Field1); } /// /// REGRESSION TEST: Comprehensive string interning edge cases. /// [TestMethod] public void StringInterning_PropertyNames_MustBeRegisteredDuringDeserialization() { var items = Enumerable.Range(0, 10).Select(i => new TestClassWithLongPropertyNames { FirstProperty = $"Value1_{i}", SecondProperty = $"Value2_{i}", ThirdProperty = $"Value3_{i}", FourthProperty = $"Value4_{i}" }).ToList(); var binary = items.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(10, result.Count); for (int i = 0; i < 10; i++) { Assert.AreEqual($"Value1_{i}", result[i].FirstProperty); Assert.AreEqual($"Value2_{i}", result[i].SecondProperty); } } [TestMethod] public void StringInterning_MixedShortAndLongStrings_HandledCorrectly() { var items = Enumerable.Range(0, 20).Select(i => new TestClassWithMixedStrings { Id = i, ShortName = $"A{i % 3}", LongName = $"LongName_{i % 5}", Description = $"Description_value_{i % 7}", Tag = i % 2 == 0 ? "AB" : "XY" }).ToList(); var binary = items.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(20, result.Count); for (int i = 0; i < 20; i++) { Assert.AreEqual(i, result[i].Id); Assert.AreEqual($"A{i % 3}", result[i].ShortName); Assert.AreEqual($"LongName_{i % 5}", result[i].LongName); Assert.AreEqual($"Description_value_{i % 7}", result[i].Description); } } [TestMethod] public void StringInterning_DeeplyNestedObjects_MaintainsCorrectOrder() { var root = new TestNestedStructure { RootName = "RootObject", Level1Items = Enumerable.Range(0, 5).Select(i => new TestLevel1 { Level1Name = $"Level1_{i}", Level2Items = Enumerable.Range(0, 3).Select(j => new TestLevel2 { Level2Name = $"Level2_{i}_{j}", Value = $"Value_{i * 3 + j}" }).ToList() }).ToList() }; var binary = root.ToBinary(); var result = binary.BinaryTo(); Assert.IsNotNull(result); Assert.AreEqual("RootObject", result.RootName); Assert.AreEqual(5, result.Level1Items.Count); for (int i = 0; i < 5; i++) { Assert.AreEqual($"Level1_{i}", result.Level1Items[i].Level1Name); Assert.AreEqual(3, result.Level1Items[i].Level2Items.Count); for (int j = 0; j < 3; j++) { Assert.AreEqual($"Level2_{i}_{j}", result.Level1Items[i].Level2Items[j].Level2Name); } } } [TestMethod] public void StringInterning_RepeatedPropertyValuesAcrossObjects_UsesReferences() { var items = Enumerable.Range(0, 50).Select(i => new TestClassWithRepeatedValues { Id = i, Status = i % 3 == 0 ? "Pending" : i % 3 == 1 ? "Processing" : "Completed", Category = i % 5 == 0 ? "CategoryA" : i % 5 == 1 ? "CategoryB" : "CategoryC", Priority = i % 2 == 0 ? "High" : "Low_Priority_Value" }).ToList(); var binary = items.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(50, result.Count); for (int i = 0; i < 50; i++) { Assert.AreEqual(i, result[i].Id); var expectedStatus = i % 3 == 0 ? "Pending" : i % 3 == 1 ? "Processing" : "Completed"; Assert.AreEqual(expectedStatus, result[i].Status, $"Status mismatch at index {i}"); } } [TestMethod] public void StringInterning_ManyUniqueStringsFollowedByRepeats_CorrectIndexLookup() { var items = new List(); for (int i = 0; i < 30; i++) { items.Add(new TestClassWithNameValue { Name = $"UniqueName_{i:D4}", Value = $"UniqueValue_{i:D4}" }); } for (int i = 0; i < 20; i++) { items.Add(new TestClassWithNameValue { Name = $"UniqueName_{i % 10:D4}", Value = $"UniqueValue_{(i + 10) % 30:D4}" }); } var binary = items.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(50, result.Count); for (int i = 0; i < 30; i++) { Assert.AreEqual($"UniqueName_{i:D4}", result[i].Name, $"Name mismatch at index {i}"); Assert.AreEqual($"UniqueValue_{i:D4}", result[i].Value, $"Value mismatch at index {i}"); } for (int i = 0; i < 20; i++) { Assert.AreEqual($"UniqueName_{i % 10:D4}", result[30 + i].Name, $"Name mismatch at index {30 + i}"); } } [TestMethod] public void StringInterning_EmptyAndNullStrings_DoNotAffectInternTable() { var items = new List(); for (int i = 0; i < 25; i++) { items.Add(new TestClassWithNullableStrings { Id = i, RequiredName = $"Required_{i:D3}", OptionalName = i % 3 == 0 ? null : i % 3 == 1 ? "" : $"Optional_{i:D3}", Description = i % 2 == 0 ? $"Description_{i % 5:D3}" : null }); } var binary = items.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result); Assert.AreEqual(25, result.Count); for (int i = 0; i < 25; i++) { Assert.AreEqual(i, result[i].Id); Assert.AreEqual($"Required_{i:D3}", result[i].RequiredName); if (i % 3 == 0) { Assert.IsNull(result[i].OptionalName, $"OptionalName at index {i} should be null"); } else if (i % 3 == 1) { Assert.IsTrue(string.IsNullOrEmpty(result[i].OptionalName), $"OptionalName at index {i} should be null or empty, got: '{result[i].OptionalName}'"); } else { Assert.AreEqual($"Optional_{i:D3}", result[i].OptionalName, $"OptionalName at index {i} mismatch"); } } } [TestMethod] public void StringInterning_ProductionLikeCustomerDto_RoundTrip() { var customers = CreateCustomerLikeItems(25); var binary = customers.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result, "Result should not be null - deserialization failed"); Assert.AreEqual(25, result.Count); for (int i = 0; i < 25; i++) { Assert.AreEqual(i, result[i].Id, $"Id mismatch at index {i}"); Assert.AreEqual($"FirstName_{i % 10}", result[i].FirstName, $"FirstName mismatch at index {i}"); Assert.AreEqual($"LastName_{i % 8}", result[i].LastName, $"LastName mismatch at index {i}"); Assert.AreEqual($"user{i}@example.com", result[i].Email, $"Email mismatch at index {i}"); Assert.AreEqual(4, result[i].Attributes.Count, $"Attributes count mismatch at index {i}"); Assert.AreEqual("DateOfReceipt", result[i].Attributes[0].Key); Assert.AreEqual("Priority", result[i].Attributes[1].Key); } } [TestMethod] public void StringInterning_LargeListWithHighStringReuse_HandlesCorrectly() { const int itemCount = 150; var items = Enumerable.Range(0, itemCount).Select(i => new TestHighReuseDto { Id = i, CategoryCode = $"CAT_{i % 10:D2}", StatusCode = $"STATUS_{i % 5:D2}", TypeCode = $"TYPE_{i % 3:D2}", PriorityCode = i % 2 == 0 ? "PRIORITY_HIGH" : "PRIORITY_LOW", UniqueField = $"UNIQUE_{i:D4}" }).ToList(); var binary = items.ToBinary(); var result = binary.BinaryTo>(); Assert.IsNotNull(result, "Result should not be null"); Assert.AreEqual(itemCount, result.Count, $"Expected {itemCount} items"); for (int i = 0; i < itemCount; i++) { Assert.AreEqual(i, result[i].Id, $"Id mismatch at {i}"); Assert.AreEqual($"CAT_{i % 10:D2}", result[i].CategoryCode, $"CategoryCode mismatch at {i}"); Assert.AreEqual($"STATUS_{i % 5:D2}", result[i].StatusCode, $"StatusCode mismatch at {i}"); Assert.AreEqual($"TYPE_{i % 3:D2}", result[i].TypeCode, $"TypeCode mismatch at {i}"); Assert.AreEqual($"UNIQUE_{i:D4}", result[i].UniqueField, $"UniqueField mismatch at {i}"); } } }