AyCode.Core/AyCode.Core.Tests/Serialization/AcBinarySerializerChainRefe...

234 lines
8.7 KiB
C#

using AyCode.Core.Extensions;
using AyCode.Core.Tests.TestModels;
namespace AyCode.Core.Tests.Serialization;
/// <summary>
/// Tests for Chain API reference preservation with IId objects.
/// This is the critical feature for DevExpress DXGrid GridCustomDataSource scenario.
/// </summary>
[TestClass]
public class AcBinarySerializerChainReferenceTests
{
/// <summary>
/// CRITICAL TEST: DevExpress DXGrid scenario with Chain API.
/// Server returns List&lt;Item&gt; for grid display, but we also have internal cache List&lt;Item&gt;.
/// When using ThenPopulate, the grid's visible items MUST be the same object references
/// from the cache to ensure Blazor binding works correctly.
/// </summary>
[TestMethod]
public void ChainPopulate_IIdObjects_PreservesReferences()
{
// Setup: Create internal cache with 5 categories
var internalCache = new List<SharedCategory_All_True>
{
new() { Id = 1, Name = "Category1", SortOrder = 1 },
new() { Id = 2, Name = "Category2", SortOrder = 2 },
new() { Id = 3, Name = "Category3", SortOrder = 3 },
new() { Id = 4, Name = "Category4", SortOrder = 4 },
new() { Id = 5, Name = "Category5", SortOrder = 5 }
};
// Server returns subset of categories (like grid pagination - page 2: items 3-5)
var serverData = new List<SharedCategory_All_True>
{
new() { Id = 3, Name = "Category3_Updated", SortOrder = 33 },
new() { Id = 4, Name = "Category4_Updated", SortOrder = 44 },
new() { Id = 5, Name = "Category5_Updated", SortOrder = 55 }
};
// Serialize server response
var binary = serverData.ToBinary();
// Grid's visible list (empty initially)
var gridVisibleList = new List<SharedCategory_All_True>();
// CRITICAL: Use Chain API to parse once, populate both cache and grid
using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();
// First: Update internal cache (will become 3 items: 3-5 updated)
chain.ThenPopulate(internalCache);
// Second: Populate grid's visible list
chain.ThenPopulate(gridVisibleList);
// VERIFICATION: After ThenPopulate, internalCache contains the 3 items from server
Assert.AreEqual(3, gridVisibleList.Count);
Assert.AreEqual(3, internalCache.Count, "ThenPopulate replaces list contents with server data");
// CRITICAL ASSERTION: Grid items MUST be same object references as cache items!
Assert.AreSame(internalCache[0], gridVisibleList[0],
"Grid item MUST be same reference as cache item for Blazor binding!");
Assert.AreSame(internalCache[1], gridVisibleList[1],
"Grid item MUST be same reference as cache item for Blazor binding!");
Assert.AreSame(internalCache[2], gridVisibleList[2],
"Grid item MUST be same reference as cache item for Blazor binding!");
// Verify data was updated correctly
Assert.AreEqual(3, internalCache[0].Id);
Assert.AreEqual("Category3_Updated", internalCache[0].Name);
Assert.AreEqual(33, internalCache[0].SortOrder);
}
/// <summary>
/// Test JSON Chain API reference preservation.
/// </summary>
[TestMethod]
public void JsonChainPopulate_IIdObjects_PreservesReferences()
{
// Setup: Create internal cache
var internalCache = new List<SharedCategory_All_True>
{
new() { Id = 1, Name = "Category1", SortOrder = 1 },
new() { Id = 2, Name = "Category2", SortOrder = 2 },
new() { Id = 3, Name = "Category3", SortOrder = 3 }
};
// Server returns subset
var serverData = new List<SharedCategory_All_True>
{
new() { Id = 2, Name = "Category2_Updated", SortOrder = 22 },
new() { Id = 3, Name = "Category3_Updated", SortOrder = 33 }
};
// Serialize server response
var json = serverData.ToJson();
// Grid's visible list
var gridVisibleList = new List<SharedCategory_All_True>();
// Use JSON Chain API
using var chain = json.JsonToChain<List<SharedCategory_All_True>>();
// Update internal cache (will replace with 2 items)
chain.ThenPopulate(internalCache);
// Populate grid's visible list
chain.ThenPopulate(gridVisibleList);
// VERIFICATION
Assert.AreEqual(2, gridVisibleList.Count);
Assert.AreEqual(2, internalCache.Count, "ThenPopulate replaces list contents");
// CRITICAL: Same references!
Assert.AreSame(internalCache[0], gridVisibleList[0]);
Assert.AreSame(internalCache[1], gridVisibleList[1]);
// Verify updates
Assert.AreEqual(2, internalCache[0].Id);
Assert.AreEqual("Category2_Updated", internalCache[0].Name);
Assert.AreEqual(22, internalCache[0].SortOrder);
}
/// <summary>
/// Test with Guid-based IId implementation.
/// </summary>
[TestMethod]
public void ChainPopulate_GuidIId_PreservesReferences()
{
var cache = new List<TestGuidOrder>
{
new() { Id = Guid.NewGuid(), Code = "ORD-001", Count = 10 },
new() { Id = Guid.NewGuid(), Code = "ORD-002", Count = 20 }
};
var id1 = cache[0].Id;
var id2 = cache[1].Id;
var serverData = new List<TestGuidOrder>
{
new() { Id = id1, Code = "ORD-001-UPDATED", Count = 11 },
new() { Id = id2, Code = "ORD-002-UPDATED", Count = 22 }
};
var binary = serverData.ToBinary();
var gridList = new List<TestGuidOrder>();
using var chain = binary.BinaryToChain<List<TestGuidOrder>>();
chain.ThenPopulate(cache);
chain.ThenPopulate(gridList);
Assert.AreEqual(2, gridList.Count);
Assert.AreSame(cache[0], gridList[0], "Guid-based IId should also preserve references");
Assert.AreSame(cache[1], gridList[1]);
Assert.AreEqual("ORD-001-UPDATED", cache[0].Code);
}
/// <summary>
/// Test multiple chain operations with different subsets.
/// </summary>
[TestMethod]
public void ChainPopulate_MultipleSubsets_PreservesReferencesAcrossAll()
{
// Large internal cache
var internalCache = Enumerable.Range(1, 10)
.Select(i => new SharedCategory_All_True { Id = i, Name = $"Category{i}", SortOrder = i * 10 })
.ToList();
// Server returns items 3-7
var serverData = Enumerable.Range(3, 5)
.Select(i => new SharedCategory_All_True { Id = i, Name = $"Category{i}_Updated", SortOrder = i * 11 })
.ToList();
var binary = serverData.ToBinary();
// Three different grid pages/views
var gridPage1 = new List<SharedCategory_All_True>();
var gridPage2 = new List<SharedCategory_All_True>();
var gridPage3 = new List<SharedCategory_All_True>();
using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();
// Update cache first
chain.ThenPopulate(internalCache);
// Populate different grid pages
chain.ThenPopulate(gridPage1);
chain.ThenPopulate(gridPage2);
chain.ThenPopulate(gridPage3);
// All pages should have same references
Assert.AreEqual(5, gridPage1.Count);
Assert.AreEqual(5, gridPage2.Count);
Assert.AreEqual(5, gridPage3.Count);
// All three pages point to the SAME objects
for (int i = 0; i < 5; i++)
{
Assert.AreSame(gridPage1[i], gridPage2[i], $"Page1 and Page2 item {i} must be same reference");
Assert.AreSame(gridPage2[i], gridPage3[i], $"Page2 and Page3 item {i} must be same reference");
Assert.AreSame(internalCache[i], gridPage1[i], $"Cache and Page1 item {i} must be same reference");
}
}
/// <summary>
/// Simple debug test to verify chain reference tracking works.
/// </summary>
[TestMethod]
public void ChainPopulate_SimpleCase_Works()
{
var list1 = new List<SharedCategory_All_True>();
var list2 = new List<SharedCategory_All_True>();
var serverData = new List<SharedCategory_All_True>
{
new() { Id = 1, Name = "Cat1", SortOrder = 10 }
};
var binary = serverData.ToBinary();
using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();
// First populate
chain.ThenPopulate(list1);
Assert.AreEqual(1, list1.Count);
Assert.AreEqual(1, list1[0].Id);
// Second populate - should reuse same object
chain.ThenPopulate(list2);
Assert.AreEqual(1, list2.Count);
Assert.AreSame(list1[0], list2[0], "Should be same object reference!");
}
}