maksit-core/src/MaksIT.Core.Tests/Extensions/ObjectExtensionsTests.cs

324 lines
8.5 KiB
C#

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<DateTime> {
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<JsonConverter> { 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<JsonConverter> { 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<Person>? 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<Person> { 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));
}
}
}