using FluentAssertions; using Microsoft.Data.SqlClient; using SIGCM2.Application.Abstractions.Persistence; using SIGCM2.Application.Common; using SIGCM2.Domain.Exceptions; using SIGCM2.Domain.Fiscal; using SIGCM2.Infrastructure.Persistence; using IibbEntity = SIGCM2.Domain.Entities.IngresosBrutos; namespace SIGCM2.Application.Tests.Infrastructure; /// /// Integration tests for IngresosBrutosRepository against SIGCM2_Test. /// NOTE: IngresosBrutos is in Respawn TablesToIgnore (seed data must survive resets). /// Tests insert their own rows and identify them by the returned Id. /// Provincia + VigenciaDesde combos chosen to be unique per test to avoid UQ violations. /// [Collection("Database")] public class IngresosBrutosRepositoryTests : IAsyncLifetime { private const string ConnectionString = "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; private SqlConnection _connection = null!; private IIngresosBrutosRepository _repo = null!; private static int _testCounter = 0; private static DateOnly NextUniqueDate() { var counter = Interlocked.Increment(ref _testCounter); // Start at 2091-01-01 to avoid clashing with TipoDeIvaRepositoryTests (2090-01-01) return new DateOnly(2091, 1, 1).AddDays(counter); } public async Task InitializeAsync() { _connection = new SqlConnection(ConnectionString); await _connection.OpenAsync(); var factory = new SqlConnectionFactory(ConnectionString); _repo = new IngresosBrutosRepository(factory); } public async Task DisposeAsync() { await _connection.CloseAsync(); await _connection.DisposeAsync(); } // ── T400.10 / T400.12: InsertAsync + GetByIdAsync ───────────────────────── [Fact] public async Task InsertAsync_ReturnsPositiveId_AndRowIsVisibleInGetById() { var vigencia = NextUniqueDate(); // Use a province not in the seed or use a date far enough to avoid UQ clash var entity = IibbEntity.ForCreation( provincia: ProvinciaArgentina.Cordoba, descripcion: "Test IIBB Cordoba", alicuota: 3.5m, vigenciaDesde: vigencia); var id = await _repo.InsertAsync(entity); id.Should().BeGreaterThan(0); var fetched = await _repo.GetByIdAsync(id); fetched.Should().NotBeNull(); fetched!.Id.Should().Be(id); fetched.Provincia.Should().Be(ProvinciaArgentina.Cordoba); fetched.Descripcion.Should().Be("Test IIBB Cordoba"); fetched.Alicuota.Should().Be(3.5m); fetched.Activo.Should().BeTrue(); fetched.VigenciaDesde.Should().Be(vigencia); fetched.VigenciaHasta.Should().BeNull(); fetched.PredecesorId.Should().BeNull(); } [Fact] public async Task GetByIdAsync_NonExistentId_ReturnsNull() { var result = await _repo.GetByIdAsync(999_999_998); result.Should().BeNull(); } // ── InsertAsync con Provincia duplicada en misma VigenciaDesde ──────────── [Fact] public async Task InsertAsync_DuplicateProvincia_SameVigenciaDesde_ThrowsDuplicateProvinciaException() { var vigencia = NextUniqueDate(); var first = IibbEntity.ForCreation(ProvinciaArgentina.Mendoza, "Primero", 2m, vigencia); await _repo.InsertAsync(first); var second = IibbEntity.ForCreation(ProvinciaArgentina.Mendoza, "Segundo", 3m, vigencia); var act = async () => await _repo.InsertAsync(second); await act.Should().ThrowAsync(); } // ── UpdateCosmeticoAsync ────────────────────────────────────────────────── [Fact] public async Task UpdateCosmeticoAsync_ChangesOnlyCosmeticFields_NotAlicuotaOrProvincia() { var vigencia = NextUniqueDate(); var entity = IibbEntity.ForCreation(ProvinciaArgentina.Salta, "Original Salta", 1.5m, vigencia); var id = await _repo.InsertAsync(entity); var result = await _repo.UpdateCosmeticoAsync(id, "Updated Salta", true); result.Should().BeTrue(); var fetched = await _repo.GetByIdAsync(id); fetched!.Descripcion.Should().Be("Updated Salta"); fetched.Activo.Should().BeTrue(); // Immutable fields must NOT change fetched.Alicuota.Should().Be(1.5m); fetched.Provincia.Should().Be(ProvinciaArgentina.Salta); fetched.VigenciaDesde.Should().Be(vigencia); fetched.VigenciaHasta.Should().BeNull(); } // ── UpdateCierreVigenciaAsync ───────────────────────────────────────────── [Fact] public async Task UpdateCierreVigenciaAsync_SetsVigenciaHasta_WhenRowIsOpen() { var vigencia = NextUniqueDate(); var entity = IibbEntity.ForCreation(ProvinciaArgentina.Tucuman, "Cierre test", 2m, vigencia); var id = await _repo.InsertAsync(entity); var closeDate = vigencia.AddMonths(6); var result = await _repo.UpdateCierreVigenciaAsync(id, closeDate); result.Should().BeTrue(); var fetched = await _repo.GetByIdAsync(id); fetched!.VigenciaHasta.Should().Be(closeDate); } [Fact] public async Task UpdateCierreVigenciaAsync_DoubleClose_ReturnsFalseOnSecondCall() { var vigencia = NextUniqueDate(); var entity = IibbEntity.ForCreation(ProvinciaArgentina.Jujuy, "Double close", 1m, vigencia); var id = await _repo.InsertAsync(entity); var closeDate = vigencia.AddMonths(6); var first = await _repo.UpdateCierreVigenciaAsync(id, closeDate); var second = await _repo.UpdateCierreVigenciaAsync(id, closeDate.AddMonths(1)); first.Should().BeTrue(); second.Should().BeFalse("the guard prevents double-close: WHERE VigenciaHasta IS NULL"); } // ── SetActivoAsync ──────────────────────────────────────────────────────── [Fact] public async Task SetActivoAsync_FalseAndBack_TogglesActivoCorrectly() { var vigencia = NextUniqueDate(); var entity = IibbEntity.ForCreation(ProvinciaArgentina.Chaco, "Toggle test", 0m, vigencia); var id = await _repo.InsertAsync(entity); await _repo.SetActivoAsync(id, false); var inactive = await _repo.GetByIdAsync(id); inactive!.Activo.Should().BeFalse(); await _repo.SetActivoAsync(id, true); var active = await _repo.GetByIdAsync(id); active!.Activo.Should().BeTrue(); } // ── ListAsync con paginación + filtros ──────────────────────────────────── [Fact] public async Task ListAsync_WithActivoFilter_ReturnsOnlyMatchingRows() { var d1 = NextUniqueDate(); var d2 = NextUniqueDate(); var activeId = await _repo.InsertAsync( IibbEntity.ForCreation(ProvinciaArgentina.Misiones, "Active row", 1m, d1)); var inactiveId = await _repo.InsertAsync( IibbEntity.ForCreation(ProvinciaArgentina.Misiones, "Inactive row", 1m, d2)); await _repo.SetActivoAsync(inactiveId, false); var result = await _repo.ListAsync(new IngresosBrutosQuery( Page: 1, PageSize: 100, Activo: true, Provincia: null)); result.Items.Should().Contain(x => x.Id == activeId); result.Items.Should().NotContain(x => x.Id == inactiveId); } [Fact] public async Task ListAsync_WithProvinciaFilter_ReturnsOnlyMatchingRows() { var vigencia = NextUniqueDate(); var id = await _repo.InsertAsync( IibbEntity.ForCreation(ProvinciaArgentina.Formosa, "Formosa row", 0m, vigencia)); var result = await _repo.ListAsync(new IngresosBrutosQuery( Page: 1, PageSize: 100, Activo: null, Provincia: ProvinciaArgentina.Formosa)); result.Total.Should().BeGreaterThan(0); result.Items.Should().Contain(x => x.Id == id); result.Items.Should().AllSatisfy(x => x.Provincia.Should().Be(ProvinciaArgentina.Formosa)); } // ── Cadena de 3 versiones ───────────────────────────────────────────────── [Fact] public async Task VersionChain_ThreeVersions_PredecesorIdChainIsCorrect() { var v1Date = NextUniqueDate(); var v1 = IibbEntity.ForCreation(ProvinciaArgentina.LaPampa, "v1", 1m, v1Date); var v1Id = await _repo.InsertAsync(v1); var v2Date = v1Date.AddMonths(1); await _repo.UpdateCierreVigenciaAsync(v1Id, v2Date.AddDays(-1)); var v2 = IibbEntity.ForCreation(ProvinciaArgentina.LaPampa, "v2", 2m, v2Date, null, v1Id); var v2Id = await _repo.InsertAsync(v2); var v3Date = v2Date.AddMonths(1); await _repo.UpdateCierreVigenciaAsync(v2Id, v3Date.AddDays(-1)); var v3 = IibbEntity.ForCreation(ProvinciaArgentina.LaPampa, "v3", 3m, v3Date, null, v2Id); var v3Id = await _repo.InsertAsync(v3); var fv2 = await _repo.GetByIdAsync(v2Id); var fv3 = await _repo.GetByIdAsync(v3Id); fv2!.PredecesorId.Should().Be(v1Id); fv3!.PredecesorId.Should().Be(v2Id); } // ── GetHistorialAsync ───────────────────────────────────────────────────── [Fact] public async Task GetHistorialAsync_ReturnsChainFromRootToId_OrderedByVigenciaDesdeAsc() { var v1Date = NextUniqueDate(); var v1 = IibbEntity.ForCreation(ProvinciaArgentina.LaRioja, "Hist v1", 1m, v1Date); var v1Id = await _repo.InsertAsync(v1); var v2Date = v1Date.AddMonths(1); await _repo.UpdateCierreVigenciaAsync(v1Id, v2Date.AddDays(-1)); var v2 = IibbEntity.ForCreation(ProvinciaArgentina.LaRioja, "Hist v2", 2m, v2Date, null, v1Id); var v2Id = await _repo.InsertAsync(v2); var v3Date = v2Date.AddMonths(1); await _repo.UpdateCierreVigenciaAsync(v2Id, v3Date.AddDays(-1)); var v3 = IibbEntity.ForCreation(ProvinciaArgentina.LaRioja, "Hist v3", 3m, v3Date, null, v2Id); var v3Id = await _repo.InsertAsync(v3); var historial = await _repo.GetHistorialAsync(v3Id); historial.Should().HaveCount(3); historial[0].Id.Should().Be(v1Id, "root is first"); historial[1].Id.Should().Be(v2Id); historial[2].Id.Should().Be(v3Id, "requested Id is last"); historial[0].VigenciaDesde.Should().BeBefore(historial[1].VigenciaDesde); historial[1].VigenciaDesde.Should().BeBefore(historial[2].VigenciaDesde); // Provincia preserved correctly across mapping historial.Should().AllSatisfy(x => x.Provincia.Should().Be(ProvinciaArgentina.LaRioja)); } [Fact] public async Task GetHistorialAsync_SingleVersion_ReturnsListWithOneItem() { var vigencia = NextUniqueDate(); var entity = IibbEntity.ForCreation(ProvinciaArgentina.Neuquen, "Solo", 1m, vigencia); var id = await _repo.InsertAsync(entity); var historial = await _repo.GetHistorialAsync(id); historial.Should().HaveCount(1); historial[0].Id.Should().Be(id); } }