using AyCode.Core.Extensions;
using AyCode.Core.Tests.TestModels;
namespace AyCode.Core.Tests.Serialization;
///
/// Tests for IId-based reference handling in JSON serializer.
/// Two scenarios:
/// 1. Same instance referenced multiple times (object identity)
/// 2. Different instances with same IId.Id (IId-based deduplication)
///
/// Tests verify BOTH:
/// - Serialized JSON uses $ref (not redundant full objects)
/// - Deserialized result maintains reference identity
///
[TestClass]
public class AcJsonSerializerIIdReferenceTests
{
#region Helper Methods
///
/// Counts occurrences of a string in JSON.
///
private static int CountOccurrences(string json, string searchString)
{
var count = 0;
var index = 0;
while ((index = json.IndexOf(searchString, index, StringComparison.Ordinal)) != -1)
{
count++;
index += searchString.Length;
}
return count;
}
#endregion
#region Scenario 1: Same Instance (Object Identity)
///
/// SCENARIO 1: Same instance referenced multiple times.
/// JSON should contain $ref for 2nd, 3rd, 4th occurrence.
/// Validates: $ref present + data integrity after deserialize + reference identity
///
[TestMethod]
public void SameInstance_Json_SerializeAndDeserialize()
{
// Arrange: SAME instance used 4 times
var sharedTag = new SharedTag_All_True { Id = 1, Name = "ImportantTag", Color = "#FF0000" };
var order = new TestOrder_All_True
{
Id = 1,
OrderNumber = "ORD-001",
PrimaryTag = sharedTag,
Items =
[
new TestOrderItem_All_True { Id = 1, ProductName = "Product-A", Tag = sharedTag },
new TestOrderItem_All_True { Id = 2, ProductName = "Product-B", Tag = sharedTag },
new TestOrderItem_All_True { Id = 3, ProductName = "Product-C", Tag = sharedTag }
]
};
// Act
var json = order.ToJson();
Console.WriteLine(json);
var result = json.JsonTo();
// Assert 1: JSON contains $ref markers (reference handling is active)
var refCount = CountOccurrences(json, "{\"$ref\":\"1\"}");
Console.WriteLine($"JSON length: {json.Length} chars");
Console.WriteLine($"$ref count: {refCount}");
Assert.IsTrue(refCount >= 3,
$"Expected at least 3 $ref entries for shared tag, found {refCount}. " +
"Reference handling may not be active!");
// Assert 2: Data integrity - ALL tags are present and have correct data
Assert.IsNotNull(result, "Deserialized result is null");
Assert.IsNotNull(result.PrimaryTag, "PrimaryTag is null - data lost!");
Assert.AreEqual(1, result.PrimaryTag.Id, "PrimaryTag.Id incorrect");
Assert.AreEqual("ImportantTag", result.PrimaryTag.Name, "PrimaryTag.Name incorrect");
Assert.AreEqual("#FF0000", result.PrimaryTag.Color, "PrimaryTag.Color incorrect");
Assert.IsNotNull(result.Items, "Items is null");
Assert.AreEqual(3, result.Items.Count, "Items count incorrect");
for (var i = 0; i < 3; i++)
{
Assert.IsNotNull(result.Items[i].Tag, $"Items[{i}].Tag is null - data lost!");
Assert.AreEqual(1, result.Items[i].Tag!.Id, $"Items[{i}].Tag.Id incorrect");
Assert.AreEqual("ImportantTag", result.Items[i].Tag.Name, $"Items[{i}].Tag.Name incorrect");
}
// Assert 3: Reference identity - all should be same object reference
Assert.AreSame(result.PrimaryTag, result.Items[0].Tag,
"Item[0].Tag should be same reference as PrimaryTag");
Assert.AreSame(result.PrimaryTag, result.Items[1].Tag,
"Item[1].Tag should be same reference as PrimaryTag");
Assert.AreSame(result.PrimaryTag, result.Items[2].Tag,
"Item[2].Tag should be same reference as PrimaryTag");
}
#endregion
#region Scenario 2: Different Instances with Same IId (IId-Based Deduplication)
///
/// SCENARIO 2: DIFFERENT instances with SAME IId.Id value.
/// CRITICAL test - if IId-based deduplication works:
/// - $ref should be used in JSON
/// - Data should be complete after deserialize
/// - References should be identical (AreSame)
/// - Different TYPES with same int Id should NOT be confused!
///
[TestMethod]
public void DifferentInstances_SameIId_SerializeAndDeserialize()
{
// Arrange: DIFFERENT instances but SAME IId.Id
// CRITICAL: Multiple DIFFERENT TYPES all have Id=1 - must not be confused!
var order = new TestOrder_All_True
{
Id = 1,
OrderNumber = "ORD-001",
// All three types have Id=1 - tests (Type, Id) keying, not just Id
PrimaryTag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
Owner = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" },
Category = new SharedCategory_All_True { Id = 1, Name = "Category_Id1", SortOrder = 10 },
Items =
[
new TestOrderItem_All_True
{
Id = 1,
ProductName = "Product-A",
Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" }
},
new TestOrderItem_All_True
{
Id = 2,
ProductName = "Product-B",
Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" }
},
new TestOrderItem_All_True
{
Id = 3,
ProductName = "Product-C",
Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" }
}
]
};
// Act
var json = order.ToJson();
var result = json.JsonTo();
// Assert 1: Check if $ref is used (IId-based deduplication active)
var refCount = CountOccurrences(json, "\"$ref\"");
Console.WriteLine($"JSON length: {json.Length} chars");
Console.WriteLine($"$ref count: {refCount}");
Console.WriteLine("JSON (first 2000 chars):");
Console.WriteLine(json.Length > 2000 ? json[..2000] + "..." : json);
// 4 Tags, 4 Users - each should have 3 $refs = 6 total minimum
Assert.IsTrue(refCount >= 6,
$"CRITICAL: Expected at least 6 $ref entries (3 per type for Tag and User), found {refCount}. " +
"IId-based reference deduplication is NOT working!");
// Assert 2: Data integrity - ALL data present and correct
Assert.IsNotNull(result, "Deserialized result is null");
// Tag data
Assert.IsNotNull(result.PrimaryTag, "PrimaryTag is null - data lost!");
Assert.AreEqual(1, result.PrimaryTag.Id, "PrimaryTag.Id incorrect");
Assert.AreEqual("Tag_Id1", result.PrimaryTag.Name, "PrimaryTag.Name incorrect - might be confused with User!");
Assert.AreEqual("#FF0000", result.PrimaryTag.Color, "PrimaryTag.Color incorrect");
// User data - MUST NOT be confused with Tag (both have Id=1)
Assert.IsNotNull(result.Owner, "Owner is null - data lost!");
Assert.AreEqual(1, result.Owner.Id, "Owner.Id incorrect");
Assert.AreEqual("User_Id1", result.Owner.Username, "Owner.Username incorrect - might be confused with Tag!");
Assert.AreEqual("user1@test.com", result.Owner.Email, "Owner.Email incorrect");
// Category data - MUST NOT be confused with Tag or User (all have Id=1)
Assert.IsNotNull(result.Category, "Category is null - data lost!");
Assert.AreEqual(1, result.Category.Id, "Category.Id incorrect");
Assert.AreEqual("Category_Id1", result.Category.Name, "Category.Name incorrect - might be confused with Tag!");
Assert.AreEqual(10, result.Category.SortOrder, "Category.SortOrder incorrect");
Assert.IsNotNull(result.Items, "Items is null");
Assert.AreEqual(3, result.Items.Count, "Items count incorrect");
for (var i = 0; i < 3; i++)
{
// Tag in items
Assert.IsNotNull(result.Items[i].Tag, $"Items[{i}].Tag is null - data lost!");
Assert.AreEqual(1, result.Items[i].Tag!.Id, $"Items[{i}].Tag.Id incorrect");
Assert.AreEqual("Tag_Id1", result.Items[i].Tag.Name, $"Items[{i}].Tag.Name incorrect - confused with User?");
// User in items - MUST NOT be confused with Tag
Assert.IsNotNull(result.Items[i].Assignee, $"Items[{i}].Assignee is null - data lost!");
Assert.AreEqual(1, result.Items[i].Assignee!.Id, $"Items[{i}].Assignee.Id incorrect");
Assert.AreEqual("User_Id1", result.Items[i].Assignee.Username, $"Items[{i}].Assignee.Username incorrect - confused with Tag?");
}
// Assert 3: Reference identity - same TYPE with same Id should be same reference
// Tags with Id=1 should all be same reference
Assert.AreSame(result.PrimaryTag, result.Items[0].Tag,
"CRITICAL: Item[0].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)");
Assert.AreSame(result.PrimaryTag, result.Items[1].Tag,
"CRITICAL: Item[1].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)");
Assert.AreSame(result.PrimaryTag, result.Items[2].Tag,
"CRITICAL: Item[2].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)");
// Users with Id=1 should all be same reference
Assert.AreSame(result.Owner, result.Items[0].Assignee,
"CRITICAL: Item[0].Assignee should be same reference as Owner (same SharedUser.Id=1)");
Assert.AreSame(result.Owner, result.Items[1].Assignee,
"CRITICAL: Item[1].Assignee should be same reference as Owner (same SharedUser.Id=1)");
Assert.AreSame(result.Owner, result.Items[2].Assignee,
"CRITICAL: Item[2].Assignee should be same reference as Owner (same SharedUser.Id=1)");
// Assert 4: Different TYPES with same Id should NOT be same reference!
Assert.AreNotSame