using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using MaksIT.Core.Extensions; using Xunit; namespace MaksIT.Core.Tests.Extensions { public class ObjectExtensionsTests { private class TestObject { public required string Name { get; set; } public int Age { get; set; } public string? Address { get; set; } } private class CustomDateTimeConverter : JsonConverter { private readonly string _format; public CustomDateTimeConverter(string format) { _format = format; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var dateString = reader.GetString(); if (dateString is null) { throw new JsonException("Expected a date string but got null."); } return DateTime.ParseExact(dateString, _format, null); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString(_format)); } } [Fact] public void ToJson_WithNullObject_ShouldReturnEmptyJson() { // Arrange TestObject? obj = null; // Act var result = obj.ToJson(); // Assert Assert.Equal("{}", result); } [Fact] public void ToJson_WithSimpleObject_ShouldReturnCorrectJson() { // Arrange var obj = new TestObject { Name = "John Doe", Age = 30 }; // Act var result = obj.ToJson(); // Assert Assert.Equal("{\"name\":\"John Doe\",\"age\":30}", result); } [Fact] public void ToJson_WithObjectAndNullValues_ShouldIgnoreNullProperties() { // Arrange var obj = new TestObject { Name = "John Doe", Age = 30, Address = null }; // Act var result = obj.ToJson(); // Assert Assert.Equal("{\"name\":\"John Doe\",\"age\":30}", result); } [Fact] public void ToJson_WithCustomJsonConverter_ShouldApplyConverter() { // Arrange var obj = new DateTime(2023, 08, 30); var converters = new List { new CustomDateTimeConverter("yyyy-MM-dd") }; // Act var result = obj.ToJson(converters); // Assert Assert.Equal("\"2023-08-30\"", result); } [Fact] public void ToJson_WithComplexObjectAndConverters_ShouldSerializeCorrectly() { // Arrange var obj = new { Name = "Jane Doe", BirthDate = new DateTime(1990, 12, 25) }; var converters = new List { new CustomDateTimeConverter("yyyy/MM/dd") }; // Act var result = obj.ToJson(converters); // Assert Assert.Equal("{\"name\":\"Jane Doe\",\"birthDate\":\"1990/12/25\"}", result); } [Fact] public void ToJson_WithEmptyObject_ShouldReturnEmptyJsonObject() { // Arrange var obj = new { }; // Act var result = obj.ToJson(); // Assert Assert.Equal("{}", result); } // ------- DeepClone / DeepEqual / RevertFrom tests below ------- private class Person { public string Name = ""; public int Age; private string _secret = "xyz"; public string Secret => _secret; public void SetSecret(string s) { _secret = s; } public Address? Addr; } private class Address { public string City = ""; public Person? Owner; // cycle back to person } private struct Score { public int A; public List? People; // ref-type field inside struct } [Fact] public void DeepClone_WithSimpleGraph_ShouldProduceIndependentCopy() { // Arrange var p = new Person { Name = "Alice", Age = 25, Addr = new Address { City = "Rome" } }; // Act var clone = p.DeepClone(); // Assert Assert.NotSame(p, clone); Assert.Equal("Alice", clone.Name); Assert.Equal(25, clone.Age); Assert.NotSame(p.Addr, clone.Addr); Assert.Equal("Rome", clone.Addr!.City); // Mutate clone should not affect original clone.Name = "Bob"; clone.Addr.City = "Milan"; clone.SetSecret("new"); Assert.Equal("Alice", p.Name); Assert.Equal("Rome", p.Addr!.City); Assert.Equal("xyz", p.Secret); } [Fact] public void DeepClone_ShouldPreserveCyclesAndReferenceIdentity() { // Arrange var p = new Person { Name = "Root" }; var a = new Address { City = "Naples" }; p.Addr = a; a.Owner = p; // create cycle // Act var clone = p.DeepClone(); // Assert Assert.NotSame(p, clone); Assert.NotSame(p.Addr, clone.Addr); Assert.Same(clone, clone.Addr!.Owner); // cycle preserved in clone } [Fact] public void DeepClone_ShouldHandleStructsWithReferenceFields() { // Arrange var s = new Score { A = 7, People = new List { new Person { Name = "P1" } } }; // Act var sClone = s.DeepClone(); // Assert Assert.Equal(7, sClone.A); Assert.NotSame(s.People, sClone.People); Assert.NotSame(s.People![0], sClone.People![0]); Assert.Equal("P1", sClone.People[0].Name); } [Fact] public void DeepClone_ShouldHandleArraysAndMultiDimensional() { // Arrange var arr = new[] { new Person { Name = "A" }, new Person { Name = "B" } }; var md = (Person[,])Array.CreateInstance(typeof(Person), new[] { 1, 2 }, new[] { 1, 1 }); md[1, 1] = arr[0]; md[1, 2] = arr[1]; // Act var arrClone = arr.DeepClone(); var mdClone = md.DeepClone(); // Assert Assert.NotSame(arr, arrClone); Assert.NotSame(arr[0], arrClone[0]); Assert.Equal("A", arrClone[0].Name); Assert.NotSame(md, mdClone); Assert.Equal(md.GetLowerBound(0), mdClone.GetLowerBound(0)); Assert.Equal(md.GetLowerBound(1), mdClone.GetLowerBound(1)); Assert.NotSame(md[1, 1], mdClone[1, 1]); Assert.Equal("A", mdClone[1, 1].Name); } [Fact] public void DeepClone_ShouldReturnSameReferenceForImmutable() { // Arrange var s = "hello"; // Act var s2 = s.DeepClone(); // Assert Assert.Same(s, s2); } [Fact] public void DeepClone_WhenNull_ReturnsDefault() { Person? source = null; var result = source.DeepClone(); Assert.Null(result); } [Fact] public void DeepEqual_ShouldReturnTrue_ForEqualGraphs() { // Arrange var p1 = new Person { Name = "Alice", Age = 30, Addr = new Address { City = "Turin" } }; var p2 = new Person { Name = "Alice", Age = 30, Addr = new Address { City = "Turin" } }; // Act var equal = p1.DeepEqual(p2); // Assert Assert.True(equal); } [Fact] public void DeepEqual_ShouldReturnFalse_WhenAnyFieldDiffers() { // Arrange var p1 = new Person { Name = "Alice", Age = 30, Addr = new Address { City = "Turin" } }; var p2 = new Person { Name = "Alice", Age = 31, Addr = new Address { City = "Turin" } }; // Act var equal = p1.DeepEqual(p2); // Assert Assert.False(equal); } [Fact] public void DeepEqual_ShouldHandleCycles() { // Arrange var p1 = new Person { Name = "R", Addr = new Address { City = "X" } }; p1.Addr!.Owner = p1; var p2 = new Person { Name = "R", Addr = new Address { City = "X" } }; p2.Addr!.Owner = p2; // Act var equal = p1.DeepEqual(p2); // Assert Assert.True(equal); } [Fact] public void RevertFrom_ShouldCopyStateBackOntoExistingInstance() { // Arrange var original = new Person { Name = "Alice", Age = 20, Addr = new Address { City = "Parma" } }; var snapshot = original.DeepClone(); // Mutate original original.Name = "Changed"; original.Age = 99; original.Addr!.City = "ChangedCity"; original.SetSecret("changed-secret"); // Act original.RevertFrom(snapshot); // Assert Assert.Equal("Alice", original.Name); Assert.Equal(20, original.Age); Assert.Equal("Parma", original.Addr!.City); Assert.Equal("xyz", original.Secret); } [Fact] public void DeepEqual_NullsAndTypeMismatch_ShouldBehaveCorrectly() { // Arrange Person? a = null; Person? b = null; var c = new Person(); var d = new TestObject { Name = "x", Age = 1 }; // Act / Assert Assert.True(a.DeepEqual(b)); Assert.False(a.DeepEqual(c)); // Different runtime types must be false Assert.False(c.DeepEqual((object)d)); } } }