using AyCode.Core.Enums; using AyCode.Core.Extensions; using AyCode.Core.Interfaces; using AyCode.Core.Loggers; using AyCode.Services.SignalRs; using AyCode.Utils.Extensions; using FruitBank.Common; using FruitBank.Common.Dtos; using FruitBank.Common.Loggers; using FruitBankHybrid.Shared.Services.SignalRs; using FruitBankHybrid.Shared.Tests; using Microsoft.Extensions.Options; using Mono.Cecil; using Newtonsoft.Json; using Nop.Core.Domain.Orders; using System.Runtime.Serialization; using AyCode.Core.Serializers.Jsons; namespace FruitBankHybrid.Shared.Tests; #region Test Models for Hybrid Reference Test /// /// Level 1 - Root entity implementing IId<int> (uses semantic ID: "Company_1") /// public class Company : IId { public int Id { get; set; } public string Name { get; set; } = string.Empty; // Collection of IId items public List Departments { get; set; } = new(); // Non-IId object (uses numeric ID) public Address HeadquartersAddress { get; set; } = new(); // Array of IId items public Employee[] BoardMembers { get; set; } = Array.Empty(); } /// /// Level 2 - Department implementing IId<int> (uses semantic ID: "Department_1") /// public class Department : IId { public int Id { get; set; } public string Name { get; set; } = string.Empty; // Back-reference to parent (should use $ref to Company semantic ID) public Company? ParentCompany { get; set; } // Collection of IId items public List Employees { get; set; } = new(); // Non-IId object (uses numeric ID) public Address? OfficeAddress { get; set; } // Array of IId items public Project[] ActiveProjects { get; set; } = Array.Empty(); } /// /// Level 3 - Employee implementing IId<int> (uses semantic ID: "Employee_1") /// public class Employee : IId { public int Id { get; set; } public string FullName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; // Back-reference to department (should use $ref to Department semantic ID) public Department? Department { get; set; } // Non-IId object (uses numeric ID) public ContactInfo? Contact { get; set; } // Collection of IId items public List AssignedProjects { get; set; } = new(); // Collection of non-IId items (uses numeric IDs) public List Skills { get; set; } = new(); } /// /// Level 4 - Project implementing IId<Guid> (uses semantic ID: "Project_guid") /// public class Project : IId { public Guid Id { get; set; } public string ProjectName { get; set; } = string.Empty; public DateTime StartDate { get; set; } // Back-reference to department (should use $ref to Department semantic ID) public Department? OwningDepartment { get; set; } // Collection of IId - circular reference to employees public List TeamMembers { get; set; } = new(); // Array of non-IId items public Milestone[] Milestones { get; set; } = Array.Empty(); // Collection of IId items public List Tasks { get; set; } = new(); } /// /// Level 5 - ProjectTask implementing IId<long> (uses semantic ID: "ProjectTask_1") /// Renamed from Task to avoid conflict with System.Threading.Tasks.Task /// public class ProjectTask : IId { public long Id { get; set; } public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public bool IsCompleted { get; set; } // Back-reference to project (should use $ref to Project semantic ID) public Project? ParentProject { get; set; } // Reference to employee (should use $ref to Employee semantic ID) public Employee? AssignedTo { get; set; } // Non-IId object public TaskMetadata? Metadata { get; set; } } /// /// Non-IId class - Address (uses standard numeric $id) /// public class Address { public string Street { get; set; } = string.Empty; public string City { get; set; } = string.Empty; public string Country { get; set; } = string.Empty; public string PostalCode { get; set; } = string.Empty; } /// /// Non-IId class - ContactInfo (uses standard numeric $id) /// public class ContactInfo { public string Phone { get; set; } = string.Empty; public string Mobile { get; set; } = string.Empty; // Shared address reference (should use $ref to numeric ID) public Address? HomeAddress { get; set; } } /// /// Non-IId class - Skill (uses standard numeric $id) /// public class Skill { public string Name { get; set; } = string.Empty; public int Level { get; set; } } /// /// Non-IId class - Milestone (uses standard numeric $id) /// public class Milestone { public string Name { get; set; } = string.Empty; public DateTime DueDate { get; set; } public bool IsCompleted { get; set; } } /// /// Non-IId class - TaskMetadata (uses standard numeric $id) /// public class TaskMetadata { public DateTime CreatedAt { get; set; } public DateTime? ModifiedAt { get; set; } public string CreatedBy { get; set; } = string.Empty; public int Priority { get; set; } public string[] Tags { get; set; } = Array.Empty(); } #endregion [TestClass] public sealed class JsonExtensionTests { private FruitBankSignalRClient _signalRClient = null!; [TestInitialize] public void TestInit() { if (!FruitBankConstClient.BaseUrl.Contains("localhost:")) throw new Exception("NEM LOCALHOST-ON TESZTELÜNK!"); _signalRClient = new FruitBankSignalRClient(new List { new SignaRClientLogItemWriter(AppType.TestUnit, LogLevel.Detail, nameof(FruitBankClientTests)) }); } [TestMethod] public async Task GetMeasuringUsersTest() { var users = await _signalRClient.GetMeasuringUsers(); Assert.IsNotNull(users); Assert.IsTrue(users.Count != 0); Assert.IsTrue(users.All(x => !x.Email.IsNullOrEmpty() && !x.Deleted)); } [TestMethod] public async Task RealOrderDto_JSON_Merge_Ref_Test() { var initialCount = 28; List? pendingOrderDtos = (await _signalRClient.GetPendingOrderDtos())?.Take(initialCount).ToList(); Assert.IsNotNull(pendingOrderDtos); Assert.IsTrue(pendingOrderDtos.Count != 0); var itemToDuplicate = pendingOrderDtos[0]; var listWithDuplication = pendingOrderDtos.ToList(); listWithDuplication.Add(itemToDuplicate); var settings = SerializeObjectExtensions.Options; var dtNow = DateTime.UtcNow; listWithDuplication[0].PaidDateUtc = dtNow; var itemWithGenericAttributes = listWithDuplication.FirstOrDefault(x => x.GenericAttributes?.Count > 0); Assert.IsNotNull(itemWithGenericAttributes, "Nincs olyan OrderDto a listában, amelynek lenne GenericAttributes eleme!"); itemWithGenericAttributes.GenericAttributes[0].CreatedOrUpdatedDateUTC = dtNow; var json = JsonConvert.SerializeObject(listWithDuplication, settings); Assert.IsTrue(json.Contains("$id"), "JSON-nak tartalmaznia kell $id tokeneket"); Assert.IsTrue(json.Contains("$ref"), "JSON-nak tartalmaznia kell $ref tokeneket"); pendingOrderDtos.DeepPopulateWithMerge(json, settings); Assert.AreEqual(initialCount, pendingOrderDtos.Count); Assert.IsTrue(itemToDuplicate.PaidDateUtc == dtNow); Assert.IsTrue(itemWithGenericAttributes.GenericAttributes[0].CreatedOrUpdatedDateUTC == dtNow); } /// /// Comprehensive test for hybrid reference handling: /// - 5 levels deep hierarchy /// - IId<int>, IId<Guid>, IId<long> types (semantic IDs) /// - Non-IId types (numeric IDs) /// - Collections (List<T>), Arrays, and single object properties /// - Back-references to parent objects /// - Shared object references /// [TestMethod] public void HybridReferenceHandling_DeepHierarchy_Test() { // Arrange - Create test data with 5 levels of depth var sharedAddress = new Address { Street = "123 Shared Street", City = "Budapest", Country = "Hungary", PostalCode = "1111" }; var company = new Company { Id = 1, Name = "Acme Corporation", HeadquartersAddress = sharedAddress }; var department1 = new Department { Id = 101, Name = "Engineering", ParentCompany = company, // Back-reference to Level 1 OfficeAddress = new Address { Street = "456 Tech Ave", City = "Budapest", Country = "Hungary", PostalCode = "2222" } }; var department2 = new Department { Id = 102, Name = "Marketing", ParentCompany = company, // Back-reference to Level 1 (same company) OfficeAddress = sharedAddress // Shared address reference }; company.Departments.Add(department1); company.Departments.Add(department2); var employee1 = new Employee { Id = 1001, FullName = "John Doe", Email = "john.doe@acme.com", Department = department1, // Back-reference to Level 2 Contact = new ContactInfo { Phone = "+36-1-111-1111", Mobile = "+36-30-111-1111", HomeAddress = sharedAddress // Shared address reference }, Skills = new List { new() { Name = "C#", Level = 5 }, new() { Name = "Azure", Level = 4 } } }; var employee2 = new Employee { Id = 1002, FullName = "Jane Smith", Email = "jane.smith@acme.com", Department = department1, // Back-reference to same department Contact = new ContactInfo { Phone = "+36-1-222-2222", Mobile = "+36-30-222-2222", HomeAddress = new Address { Street = "789 Home Lane", City = "Debrecen", Country = "Hungary", PostalCode = "3333" } }, Skills = new List { new() { Name = "JavaScript", Level = 5 }, new() { Name = "React", Level = 4 } } }; var employee3 = new Employee { Id = 1003, FullName = "Bob Wilson", Email = "bob.wilson@acme.com", Department = department2, // Different department Skills = new List { new() { Name = "Marketing", Level = 5 } } }; department1.Employees.Add(employee1); department1.Employees.Add(employee2); department2.Employees.Add(employee3); // Board members array company.BoardMembers = new[] { employee1, employee3 }; // Shared employee references var project1 = new Project { Id = Guid.NewGuid(), ProjectName = "Project Alpha", StartDate = DateTime.UtcNow.AddMonths(-3), OwningDepartment = department1, // Back-reference to Level 2 TeamMembers = new List { employee1, employee2 }, // Shared employee references Milestones = new[] { new Milestone { Name = "Phase 1", DueDate = DateTime.UtcNow.AddMonths(-2), IsCompleted = true }, new Milestone { Name = "Phase 2", DueDate = DateTime.UtcNow.AddMonths(-1), IsCompleted = true }, new Milestone { Name = "Phase 3", DueDate = DateTime.UtcNow.AddMonths(1), IsCompleted = false } } }; var project2 = new Project { Id = Guid.NewGuid(), ProjectName = "Project Beta", StartDate = DateTime.UtcNow.AddMonths(-1), OwningDepartment = department1, // Same department TeamMembers = new List { employee2 }, // Shared employee reference Milestones = new[] { new Milestone { Name = "Initial", DueDate = DateTime.UtcNow.AddMonths(2), IsCompleted = false } } }; department1.ActiveProjects = new[] { project1, project2 }; employee1.AssignedProjects.Add(project1); employee2.AssignedProjects.Add(project1); employee2.AssignedProjects.Add(project2); var task1 = new ProjectTask { Id = 10001L, Title = "Implement Feature X", Description = "Detailed implementation of Feature X", IsCompleted = false, ParentProject = project1, // Back-reference to Level 4 AssignedTo = employee1, // Reference to Level 3 Metadata = new TaskMetadata { CreatedAt = DateTime.UtcNow.AddDays(-10), CreatedBy = "admin", Priority = 1, Tags = new[] { "urgent", "feature", "backend" } } }; var task2 = new ProjectTask { Id = 10002L, Title = "Write Tests", Description = "Unit tests for Feature X", IsCompleted = false, ParentProject = project1, // Same project AssignedTo = employee2, // Different employee Metadata = new TaskMetadata { CreatedAt = DateTime.UtcNow.AddDays(-5), CreatedBy = "admin", Priority = 2, Tags = new[] { "testing", "quality" } } }; var task3 = new ProjectTask { Id = 10003L, Title = "Review Code", Description = "Code review for Feature X", IsCompleted = false, ParentProject = project1, AssignedTo = employee1, // Same employee as task1 (circular reference) Metadata = new TaskMetadata { CreatedAt = DateTime.UtcNow.AddDays(-3), CreatedBy = "lead", Priority = 1, Tags = new[] { "review" } } }; project1.Tasks.Add(task1); project1.Tasks.Add(task2); project1.Tasks.Add(task3); var settings = SerializeObjectExtensions.Options; // Act - Serialize var json = JsonConvert.SerializeObject(company, settings); // Assert - JSON structure Assert.IsNotNull(json); Assert.IsTrue(json.Length > 0, "JSON should not be empty"); // Check for semantic IDs (IId types) Assert.IsTrue(json.Contains("\"$id\":\"Company_1\""), "Should contain semantic ID for Company"); Assert.IsTrue(json.Contains("\"$id\":\"Department_101\""), "Should contain semantic ID for Department 101"); Assert.IsTrue(json.Contains("\"$id\":\"Department_102\""), "Should contain semantic ID for Department 102"); Assert.IsTrue(json.Contains("\"$id\":\"Employee_1001\""), "Should contain semantic ID for Employee 1001"); Assert.IsTrue(json.Contains("\"$id\":\"Employee_1002\""), "Should contain semantic ID for Employee 1002"); Assert.IsTrue(json.Contains("\"$id\":\"Employee_1003\""), "Should contain semantic ID for Employee 1003"); Assert.IsTrue(json.Contains("\"$id\":\"ProjectTask_10001\""), "Should contain semantic ID for ProjectTask 10001"); // Check for $ref tokens (back-references) Assert.IsTrue(json.Contains("\"$ref\":\"Company_1\""), "Should contain $ref to Company"); Assert.IsTrue(json.Contains("\"$ref\":\"Department_101\""), "Should contain $ref to Department"); Assert.IsTrue(json.Contains("\"$ref\":\"Employee_1001\""), "Should contain $ref to Employee 1001"); Assert.IsTrue(json.Contains("\"$ref\":\"Employee_1002\""), "Should contain $ref to Employee 1002"); // Check for numeric IDs (non-IId types like Address, ContactInfo, etc.) // These should have simple numeric $id values Assert.IsTrue(System.Text.RegularExpressions.Regex.IsMatch(json, @"\$id"":""[0-9]+"""), "Should contain numeric $id for non-IId types"); // Act - Deserialize var deserializedCompany = JsonConvert.DeserializeObject(json, settings); // Assert - Deserialized structure Assert.IsNotNull(deserializedCompany); Assert.AreEqual(1, deserializedCompany.Id); Assert.AreEqual("Acme Corporation", deserializedCompany.Name); // Verify departments Assert.AreEqual(2, deserializedCompany.Departments.Count); var deserializedDept1 = deserializedCompany.Departments.First(d => d.Id == 101); var deserializedDept2 = deserializedCompany.Departments.First(d => d.Id == 102); // Verify back-references to company Assert.AreSame(deserializedCompany, deserializedDept1.ParentCompany, "Department1's ParentCompany should be the same instance as deserializedCompany"); Assert.AreSame(deserializedCompany, deserializedDept2.ParentCompany, "Department2's ParentCompany should be the same instance as deserializedCompany"); // Verify employees Assert.AreEqual(2, deserializedDept1.Employees.Count); var deserializedEmp1 = deserializedDept1.Employees.First(e => e.Id == 1001); var deserializedEmp2 = deserializedDept1.Employees.First(e => e.Id == 1002); // Verify employee back-references to department Assert.AreSame(deserializedDept1, deserializedEmp1.Department, "Employee1's Department should be the same instance as deserializedDept1"); Assert.AreSame(deserializedDept1, deserializedEmp2.Department, "Employee2's Department should be the same instance as deserializedDept1"); // Verify board members are same instances as department employees Assert.AreEqual(2, deserializedCompany.BoardMembers.Length); Assert.AreSame(deserializedEmp1, deserializedCompany.BoardMembers.First(e => e.Id == 1001), "BoardMember should be the same instance as deserializedEmp1"); // Verify projects Assert.AreEqual(2, deserializedDept1.ActiveProjects.Length); var deserializedProject1 = deserializedDept1.ActiveProjects.First(p => p.ProjectName == "Project Alpha"); // Verify project back-reference to department Assert.AreSame(deserializedDept1, deserializedProject1.OwningDepartment, "Project1's OwningDepartment should be the same instance as deserializedDept1"); // Verify project team members are same instances Assert.IsTrue(deserializedProject1.TeamMembers.Any(e => ReferenceEquals(e, deserializedEmp1)), "Project1's TeamMembers should contain the same instance as deserializedEmp1"); Assert.IsTrue(deserializedProject1.TeamMembers.Any(e => ReferenceEquals(e, deserializedEmp2)), "Project1's TeamMembers should contain the same instance as deserializedEmp2"); // Verify tasks Assert.AreEqual(3, deserializedProject1.Tasks.Count); var deserializedTask1 = deserializedProject1.Tasks.First(t => t.Id == 10001L); // Verify task back-references Assert.AreSame(deserializedProject1, deserializedTask1.ParentProject, "Task1's ParentProject should be the same instance as deserializedProject1"); Assert.AreSame(deserializedEmp1, deserializedTask1.AssignedTo, "Task1's AssignedTo should be the same instance as deserializedEmp1"); // Verify shared Address instances (non-IId type) Assert.AreSame(deserializedCompany.HeadquartersAddress, deserializedDept2.OfficeAddress, "HeadquartersAddress and Dept2's OfficeAddress should be the same instance (shared Address)"); Assert.AreSame(deserializedCompany.HeadquartersAddress, deserializedEmp1.Contact?.HomeAddress, "HeadquartersAddress and Emp1's HomeAddress should be the same instance (shared Address)"); // Verify milestones (non-IId array) Assert.AreEqual(3, deserializedProject1.Milestones.Length); Assert.IsTrue(deserializedProject1.Milestones.Any(m => m.Name == "Phase 1" && m.IsCompleted)); // Verify skills (non-IId collection) Assert.AreEqual(2, deserializedEmp1.Skills.Count); Assert.IsTrue(deserializedEmp1.Skills.Any(s => s.Name == "C#" && s.Level == 5)); // Verify task metadata (non-IId object) Assert.IsNotNull(deserializedTask1.Metadata); Assert.AreEqual("admin", deserializedTask1.Metadata.CreatedBy); Assert.AreEqual(1, deserializedTask1.Metadata.Priority); Assert.AreEqual(3, deserializedTask1.Metadata.Tags.Length); } /// /// Test for DeepPopulateWithMerge with hybrid references /// [TestMethod] public void HybridReferenceHandling_DeepPopulateWithMerge_Test() { // Arrange - Create initial data var company = new Company { Id = 1, Name = "Original Company Name", HeadquartersAddress = new Address { City = "Original City" } }; var department = new Department { Id = 101, Name = "Original Department", ParentCompany = company }; company.Departments.Add(department); var employee = new Employee { Id = 1001, FullName = "Original Name", Email = "original@email.com", Department = department }; department.Employees.Add(employee); // Create modified version var modifiedCompany = new Company { Id = 1, Name = "Modified Company Name", HeadquartersAddress = new Address { City = "Modified City" } }; var modifiedDepartment = new Department { Id = 101, Name = "Modified Department", ParentCompany = modifiedCompany }; modifiedCompany.Departments.Add(modifiedDepartment); var modifiedEmployee = new Employee { Id = 1001, FullName = "Modified Name", Email = "modified@email.com", Department = modifiedDepartment }; modifiedDepartment.Employees.Add(modifiedEmployee); // Add a new employee in the modified version var newEmployee = new Employee { Id = 1002, FullName = "New Employee", Email = "new@email.com", Department = modifiedDepartment }; modifiedDepartment.Employees.Add(newEmployee); var settings = SerializeObjectExtensions.Options; var modifiedJson = JsonConvert.SerializeObject(modifiedCompany, settings); // Store original references var originalCompanyRef = company; var originalDepartmentRef = department; var originalEmployeeRef = employee; // Act - Deep populate with merge company.DeepPopulateWithMerge(modifiedJson, settings); // Assert - Same instances should be updated, not replaced Assert.AreSame(originalCompanyRef, company, "Company instance should be the same"); Assert.AreEqual("Modified Company Name", company.Name, "Company name should be updated"); Assert.AreEqual("Modified City", company.HeadquartersAddress.City, "Address should be updated"); Assert.AreEqual(1, company.Departments.Count, "Should still have 1 department"); Assert.AreSame(originalDepartmentRef, company.Departments[0], "Department instance should be the same"); Assert.AreEqual("Modified Department", company.Departments[0].Name, "Department name should be updated"); // The original employee should be updated var updatedEmployee = company.Departments[0].Employees.FirstOrDefault(e => e.Id == 1001); Assert.IsNotNull(updatedEmployee); Assert.AreSame(originalEmployeeRef, updatedEmployee, "Original employee instance should be the same"); Assert.AreEqual("Modified Name", updatedEmployee.FullName, "Employee name should be updated"); Assert.AreEqual("modified@email.com", updatedEmployee.Email, "Employee email should be updated"); // New employee should be added Assert.AreEqual(2, company.Departments[0].Employees.Count, "Should have 2 employees after merge"); var addedEmployee = company.Departments[0].Employees.FirstOrDefault(e => e.Id == 1002); Assert.IsNotNull(addedEmployee, "New employee should be added"); Assert.AreEqual("New Employee", addedEmployee.FullName); } /// /// Test that verifies circular references don't cause infinite loops /// [TestMethod] public void HybridReferenceHandling_CircularReferences_NoInfiniteLoop_Test() { // Arrange - Create circular reference structure var company = new Company { Id = 1, Name = "Test Company" }; var department = new Department { Id = 101, Name = "Test Dept", ParentCompany = company }; company.Departments.Add(department); var employee = new Employee { Id = 1001, FullName = "Test Employee", Department = department }; department.Employees.Add(employee); var project = new Project { Id = Guid.NewGuid(), ProjectName = "Test Project", OwningDepartment = department, TeamMembers = new List { employee } }; department.ActiveProjects = new[] { project }; employee.AssignedProjects.Add(project); var task = new ProjectTask { Id = 10001L, Title = "Test Task", ParentProject = project, AssignedTo = employee // Circular: Task -> Employee -> Project -> Task's Project }; project.Tasks.Add(task); var settings = SerializeObjectExtensions.Options; // Act - Should not throw StackOverflowException or timeout var json = JsonConvert.SerializeObject(company, settings); var deserialized = JsonConvert.DeserializeObject(json, settings); // Assert Assert.IsNotNull(deserialized); Assert.AreEqual(1, deserialized.Id); var deserializedDept = deserialized.Departments[0]; var deserializedEmployee = deserializedDept.Employees[0]; var deserializedProject = deserializedDept.ActiveProjects[0]; var deserializedTask = deserializedProject.Tasks[0]; // Verify circular references are correctly resolved Assert.AreSame(deserialized, deserializedDept.ParentCompany); Assert.AreSame(deserializedDept, deserializedEmployee.Department); Assert.AreSame(deserializedDept, deserializedProject.OwningDepartment); Assert.AreSame(deserializedProject, deserializedTask.ParentProject); Assert.AreSame(deserializedEmployee, deserializedTask.AssignedTo); Assert.AreSame(deserializedEmployee, deserializedProject.TeamMembers[0]); Assert.AreSame(deserializedProject, deserializedEmployee.AssignedProjects[0]); } }