using Domain; using static TestData.ClientBuilder; using static TestData.OrderBuilder; namespace Web.Tests; /// The hand-written map both ways, never mutate their input, and round-trip losslessly; domain objects come from the test-data builders, DTO initializers stay literal. public class DtoMapperTests { // (1) Mappers map all scalar fields, in both directions. [Fact] public void ToClientDto_maps_all_scalar_fields() { var id = Guid.NewGuid(); var client = ValidClient().WithId(id).WithName("Acme").Build(); var dto = DtoMappers.ToClientDto(client); Assert.Equal(id, dto.Id); Assert.Equal("Acme", dto.Name); } [Fact] public void ToOrderDto_maps_all_scalar_fields() { var id = Guid.NewGuid(); var order = ValidOrder().WithId(id).WithDescription("one").WithTotal(150_00).Build(); var dto = DtoMappers.ToOrderDto(order); Assert.Equal(id, dto.Id); Assert.Equal("one", dto.Description); Assert.Equal(150_00, dto.TotalInCents); } [Fact] public void FromClientDto_maps_all_scalar_fields() { var id = Guid.NewGuid(); var dto = new ClientDto { Id = id, Name = "Acme" }; var client = DtoMappers.FromClientDto(dto); Assert.Equal(id, client.Id); Assert.Equal("Acme", client.Name); } [Fact] public void FromOrderDto_maps_all_scalar_fields() { var id = Guid.NewGuid(); var dto = new OrderDto { Id = id, Description = "one", TotalInCents = 150_00 }; var order = DtoMappers.FromOrderDto(dto); Assert.Equal(id, order.Id); Assert.Equal("one", order.Description); Assert.Equal(150_00, order.TotalInCents); } // (2) Mappers map the relations, and the back-references stay consistent. [Fact] public void ToClientDto_maps_orders_and_keeps_the_back_reference_consistent() { var order = ValidOrder().WithDescription("one").Build(); var client = ValidClient().WithName("Acme").WithOrder(order).Build(); var dto = DtoMappers.ToClientDto(client); var orderDto = Assert.Single(dto.Orders); // The order is mapped with the client's data: Assert.Equal(order.Id, orderDto.Id); Assert.Equal(order.Description, orderDto.Description); // The back-reference cycle is resolved to the SAME dto reference: Assert.Same(dto, orderDto.ClientDto); } [Fact] public void FromClientDto_wires_the_order_client_back_reference() { var orderDto = new OrderDto { Description = "one" }; var dto = new ClientDto { Name = "Acme", Orders = new[] { orderDto } }; var client = DtoMappers.FromClientDto(dto); var order = Assert.Single(client.Orders); Assert.Equal(orderDto.Description, order.Description); // Navigation fix-up: the reconstructed order points at the client: Assert.Same(client, order.Client); } // (3) Round-trips preserve all state (compared by values, not reference). [Fact] public void Domain_round_trip_preserves_all_state() { var order = ValidOrder().WithId(Guid.NewGuid()).WithDescription("one").WithTotal(150_00).Build(); var client = ValidClient().WithId(Guid.NewGuid()).WithName("Acme").WithOrder(order).Build(); var roundTrip = DtoMappers.FromClientDto(DtoMappers.ToClientDto(client)); Assert.Equal(client.Id, roundTrip.Id); Assert.Equal(client.Name, roundTrip.Name); var roundTripOrder = Assert.Single(roundTrip.Orders); Assert.Equal(order.Id, roundTripOrder.Id); Assert.Equal(order.Description, roundTripOrder.Description); Assert.Equal(order.TotalInCents, roundTripOrder.TotalInCents); // The back-reference is still consistent on the reconstructed graph: Assert.Same(roundTrip, roundTripOrder.Client); } [Fact] public void Dto_round_trip_preserves_all_state() { var order = ValidOrder().WithId(Guid.NewGuid()).WithDescription("one").WithTotal(150_00).Build(); var client = ValidClient().WithId(Guid.NewGuid()).WithName("Acme").WithOrder(order).Build(); var dto = DtoMappers.ToClientDto(client); var dtoOrder = dto.Orders[0]; var roundTrip = DtoMappers.ToClientDto(DtoMappers.FromClientDto(dto)); Assert.Equal(dto.Id, roundTrip.Id); Assert.Equal(dto.Name, roundTrip.Name); var roundTripOrder = Assert.Single(roundTrip.Orders); Assert.Equal(dtoOrder.Id, roundTripOrder.Id); Assert.Equal(dtoOrder.Description, roundTripOrder.Description); Assert.Equal(dtoOrder.TotalInCents, roundTripOrder.TotalInCents); Assert.Same(roundTrip, roundTripOrder.ClientDto); } // (4) Mappers do not mutate their inputs (snapshot fields, map, compare). [Fact] public void ToClientDto_does_not_mutate_the_domain() { var order = ValidOrder().WithId(Guid.NewGuid()).WithDescription("one").Build(); var client = ValidClient().WithId(Guid.NewGuid()).WithName("Acme").WithOrder(order).Build(); var ordersCount = client.Orders.Count; DtoMappers.ToClientDto(client); DtoMappers.ToOrderDto(order); Assert.Equal("Acme", client.Name); Assert.Equal(ordersCount, client.Orders.Count); Assert.Same(client, order.Client); // back-reference untouched Assert.Equal("one", order.Description); } [Fact] public void FromClientDto_does_not_mutate_the_dto() { var orderDto = new OrderDto { Description = "one" }; var dto = new ClientDto { Id = Guid.NewGuid(), Name = "Acme", Orders = new[] { orderDto } }; DtoMappers.FromClientDto(dto); DtoMappers.FromOrderDto(orderDto); Assert.Equal("Acme", dto.Name); Assert.Same(orderDto, Assert.Single(dto.Orders)); // collection untouched Assert.Null(orderDto.ClientDto); // back-reference untouched Assert.Equal("one", orderDto.Description); } }