From b16dd313edf930c2f787a4a89bea35f2e1c0304e Mon Sep 17 00:00:00 2001 From: dmolinari Date: Fri, 17 Apr 2026 17:48:12 -0300 Subject: [PATCH] test(adm-009): TipoDeIva entity validation tests (Red) --- .../Domain/Fiscal/TipoDeIvaTests.cs | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 tests/SIGCM2.Application.Tests/Domain/Fiscal/TipoDeIvaTests.cs diff --git a/tests/SIGCM2.Application.Tests/Domain/Fiscal/TipoDeIvaTests.cs b/tests/SIGCM2.Application.Tests/Domain/Fiscal/TipoDeIvaTests.cs new file mode 100644 index 0000000..b5d700f --- /dev/null +++ b/tests/SIGCM2.Application.Tests/Domain/Fiscal/TipoDeIvaTests.cs @@ -0,0 +1,351 @@ +using System.Reflection; +using FluentAssertions; +using SIGCM2.Domain.Entities; +using SIGCM2.Domain.Exceptions; + +namespace SIGCM2.Application.Tests.Domain.Fiscal; + +public class TipoDeIvaTests +{ + private static readonly DateOnly Desde2020 = new(2020, 1, 1); + private static readonly DateOnly Desde2026 = new(2026, 6, 1); + + private static TipoDeIva MakeTipoDeIva( + int id = 1, + string codigo = "IVA_21", + string descripcion = "IVA 21%", + decimal porcentaje = 21m, + bool aplicaIVA = true, + bool activo = true, + DateOnly? vigenciaDesde = null, + DateOnly? vigenciaHasta = null, + int? predecesorId = null) + => TipoDeIva.FromDb( + id: id, + codigo: codigo, + descripcion: descripcion, + porcentaje: porcentaje, + aplicaIVA: aplicaIVA, + activo: activo, + vigenciaDesde: vigenciaDesde ?? Desde2020, + vigenciaHasta: vigenciaHasta, + predecesorId: predecesorId, + fechaCreacion: DateTime.UtcNow, + fechaModificacion: null); + + // ── T200.10: ForCreation validations (RED) ──────────────────────────────── + + [Fact] + public void ForCreation_ValidArgs_ReturnsEntity() + { + var iva = TipoDeIva.ForCreation("IVA_21", "IVA 21%", 21m, true, Desde2020); + + iva.Codigo.Should().Be("IVA_21"); + iva.Descripcion.Should().Be("IVA 21%"); + iva.Porcentaje.Should().Be(21m); + iva.AplicaIVA.Should().BeTrue(); + iva.Activo.Should().BeTrue(); + iva.Id.Should().Be(0); + iva.PredecesorId.Should().BeNull(); + iva.VigenciaHasta.Should().BeNull(); + } + + [Theory] + [InlineData("INVALIDO")] + [InlineData("IVA21")] + [InlineData("iva_21")] + [InlineData("IVA-21")] + [InlineData("")] + [InlineData(" ")] + public void ForCreation_CodigoInvalido_ThrowsArgumentException(string codigoInvalido) + { + var act = () => TipoDeIva.ForCreation(codigoInvalido, "desc", 21m, true, Desde2020); + + act.Should().Throw() + .WithParameterName("codigo"); + } + + [Theory] + [InlineData("EXENTO")] + [InlineData("NO_GRAVADO")] + [InlineData("IVA_21")] + [InlineData("IVA_105")] + [InlineData("IVA_0")] + public void ForCreation_CodigoValido_NoLanza(string codigoValido) + { + var act = () => TipoDeIva.ForCreation(codigoValido, "desc", 0m, false, Desde2020); + + act.Should().NotThrow(); + } + + [Fact] + public void ForCreation_PorcentajeNegativo_ThrowsArgumentException() + { + var act = () => TipoDeIva.ForCreation("IVA_21", "desc", -5m, true, Desde2020); + + act.Should().Throw() + .WithParameterName("porcentaje"); + } + + [Fact] + public void ForCreation_PorcentajeMayorA100_ThrowsArgumentException() + { + var act = () => TipoDeIva.ForCreation("IVA_21", "desc", 150m, true, Desde2020); + + act.Should().Throw() + .WithParameterName("porcentaje"); + } + + [Theory] + [InlineData(0)] + [InlineData(10.5)] + [InlineData(21)] + [InlineData(100)] + public void ForCreation_PorcentajeEnRango_NoLanza(double porcentaje) + { + var act = () => TipoDeIva.ForCreation("IVA_21", "desc", (decimal)porcentaje, true, Desde2020); + + act.Should().NotThrow(); + } + + [Fact] + public void ForCreation_VigenciaHastaMenorQueDesde_ThrowsArgumentException() + { + var desde = new DateOnly(2026, 6, 1); + var hasta = new DateOnly(2026, 1, 1); // antes que desde + + var act = () => TipoDeIva.ForCreation("IVA_21", "desc", 21m, true, desde, hasta); + + act.Should().Throw() + .WithParameterName("vigenciaHasta"); + } + + [Fact] + public void ForCreation_VigenciaHastaIgualQueDesde_NoLanza() + { + var fecha = new DateOnly(2026, 1, 1); + var act = () => TipoDeIva.ForCreation("IVA_21", "desc", 21m, true, fecha, fecha); + + act.Should().NotThrow(); + } + + // ── T200.12: ForCreation sets all properties correctly ──────────────────── + + [Fact] + public void ForCreation_SetsAllProps_Correctly() + { + var iva = TipoDeIva.ForCreation("EXENTO", "Exento de IVA", 0m, false, Desde2020); + + iva.Id.Should().Be(0); + iva.Activo.Should().BeTrue(); + iva.VigenciaHasta.Should().BeNull(); + iva.VigenciaDesde.Should().Be(Desde2020); + iva.PredecesorId.Should().BeNull(); + } + + // ── T200.11: With* methods ──────────────────────────────────────────────── + + [Fact] + public void WithDescripcion_ReturnsNewInstanceWithUpdatedDescripcion() + { + var original = MakeTipoDeIva(descripcion: "Original"); + + var updated = original.WithDescripcion("Nueva descripcion"); + + updated.Should().NotBeSameAs(original); + updated.Descripcion.Should().Be("Nueva descripcion"); + updated.Porcentaje.Should().Be(original.Porcentaje, "Porcentaje es inmutable"); + } + + [Fact] + public void WithCodigo_ReturnsNewInstanceWithUpdatedCodigo() + { + var original = MakeTipoDeIva(codigo: "IVA_21"); + + var updated = original.WithCodigo("NO_GRAVADO"); + + updated.Codigo.Should().Be("NO_GRAVADO"); + updated.Porcentaje.Should().Be(original.Porcentaje); + updated.Id.Should().Be(original.Id); + } + + [Fact] + public void WithAplicaIVA_ReturnsNewInstanceWithUpdatedAplicaIVA() + { + var original = MakeTipoDeIva(aplicaIVA: true); + + var updated = original.WithAplicaIVA(false); + + updated.AplicaIVA.Should().BeFalse(); + updated.Porcentaje.Should().Be(original.Porcentaje); + } + + [Fact] + public void Deactivate_ReturnsNewInstanceWithActivoFalse() + { + var original = MakeTipoDeIva(activo: true); + + var deactivated = original.Deactivate(); + + deactivated.Activo.Should().BeFalse(); + deactivated.Porcentaje.Should().Be(original.Porcentaje); + deactivated.Id.Should().Be(original.Id); + } + + [Fact] + public void Reactivate_ReturnsNewInstanceWithActivoTrue() + { + var original = MakeTipoDeIva(activo: false); + + var reactivated = original.Reactivate(); + + reactivated.Activo.Should().BeTrue(); + } + + [Fact] + public void CerrarVigencia_SetsVigenciaHasta() + { + var original = MakeTipoDeIva(vigenciaHasta: null); + var hasta = new DateOnly(2026, 5, 31); + + var cerrado = original.CerrarVigencia(hasta); + + cerrado.VigenciaHasta.Should().Be(hasta); + cerrado.Porcentaje.Should().Be(original.Porcentaje); + cerrado.Id.Should().Be(original.Id); + } + + // ── T200.13 & T200.15: NuevaVersion tuple (RED then GREEN) ─────────────── + + [Fact] + public void NuevaVersion_ReturnsPredecesoraCerradaYNuevaVersion() + { + var predecesora = MakeTipoDeIva(id: 5, porcentaje: 21m, vigenciaDesde: Desde2020, vigenciaHasta: null); + + var (cerrada, nueva) = predecesora.NuevaVersion(23.5m, Desde2026); + + cerrada.Id.Should().Be(5); + cerrada.VigenciaHasta.Should().Be(Desde2026.AddDays(-1), "predecesora queda cerrada el día anterior"); + cerrada.Porcentaje.Should().Be(21m, "porcentaje predecesora no cambia"); + + nueva.Id.Should().Be(0, "ID lo asigna la BD"); + nueva.PredecesorId.Should().Be(5); + nueva.Porcentaje.Should().Be(23.5m); + nueva.VigenciaDesde.Should().Be(Desde2026); + nueva.VigenciaHasta.Should().BeNull("nueva versión nace abierta"); + nueva.Codigo.Should().Be(predecesora.Codigo, "hereda el código"); + nueva.Descripcion.Should().Be(predecesora.Descripcion, "hereda la descripción"); + nueva.Activo.Should().BeTrue(); + } + + [Fact] + public void NuevaVersion_PorcentajeDistinto_EsIndependiente() + { + var predecesora = MakeTipoDeIva(porcentaje: 10.5m, vigenciaDesde: Desde2020); + var nuevaVigencia = new DateOnly(2025, 1, 1); + + var (_, nueva) = predecesora.NuevaVersion(21m, nuevaVigencia); + + nueva.Porcentaje.Should().Be(21m); + predecesora.Porcentaje.Should().Be(10.5m, "predecesora no muta"); + } + + // ── T200.13: NuevaVersion validations ──────────────────────────────────── + + [Fact] + public void NuevaVersion_PredecesoraConVigenciaHasta_ThrowsInvalidOperationException() + { + var predecesora = MakeTipoDeIva( + vigenciaDesde: Desde2020, + vigenciaHasta: new DateOnly(2025, 12, 31)); // ya cerrada + + var act = () => predecesora.NuevaVersion(23.5m, Desde2026); + + act.Should().Throw(); + } + + [Fact] + public void NuevaVersion_VigenciaDesdeIgualAPredecesora_ThrowsArgumentException() + { + var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2020, vigenciaHasta: null); + + var act = () => predecesora.NuevaVersion(23.5m, Desde2020); // igual a VigenciaDesde predecesora + + act.Should().Throw() + .WithParameterName("vigenciaDesde"); + } + + [Fact] + public void NuevaVersion_VigenciaDesDeMenorQuePredecesora_ThrowsArgumentException() + { + var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2026, vigenciaHasta: null); + var vigenciaAnterior = new DateOnly(2020, 1, 1); + + var act = () => predecesora.NuevaVersion(23.5m, vigenciaAnterior); + + act.Should().Throw() + .WithParameterName("vigenciaDesde"); + } + + [Fact] + public void NuevaVersion_NuevoPorcentajeNegativo_ThrowsArgumentException() + { + var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2020, vigenciaHasta: null); + + var act = () => predecesora.NuevaVersion(-1m, Desde2026); + + act.Should().Throw() + .WithParameterName("nuevoPorcentaje"); + } + + [Fact] + public void NuevaVersion_NuevoPorcentajeMayorA100_ThrowsArgumentException() + { + var predecesora = MakeTipoDeIva(vigenciaDesde: Desde2020, vigenciaHasta: null); + + var act = () => predecesora.NuevaVersion(101m, Desde2026); + + act.Should().Throw() + .WithParameterName("nuevoPorcentaje"); + } + + // ── T200.16: reflection — NO debe existir WithPorcentaje ───────────────── + + [Fact] + public void TipoDeIva_No_Debe_Exponer_WithPorcentaje() + { + var method = typeof(TipoDeIva).GetMethod("WithPorcentaje", BindingFlags.Public | BindingFlags.Instance); + + method.Should().BeNull("Porcentaje es inmutable — usar NuevaVersion"); + } + + // ── Immutable fields preserved across With* ─────────────────────────────── + + [Fact] + public void WithDescripcion_PreservesImmutableFields() + { + var original = MakeTipoDeIva(id: 99, porcentaje: 21m, vigenciaDesde: Desde2020); + + var updated = original.WithDescripcion("Nueva"); + + updated.Id.Should().Be(99); + updated.Porcentaje.Should().Be(21m); + updated.VigenciaDesde.Should().Be(Desde2020); + } + + [Fact] + public void FromDb_SetsAllProperties() + { + var fechaCreacion = DateTime.UtcNow; + var iva = TipoDeIva.FromDb( + id: 7, codigo: "IVA_21", descripcion: "IVA 21%", + porcentaje: 21m, aplicaIVA: true, activo: true, + vigenciaDesde: Desde2020, vigenciaHasta: null, + predecesorId: null, fechaCreacion: fechaCreacion, fechaModificacion: null); + + iva.Id.Should().Be(7); + iva.Codigo.Should().Be("IVA_21"); + iva.Porcentaje.Should().Be(21m); + iva.FechaCreacion.Should().Be(fechaCreacion); + } +}