297 lines
12 KiB
C#
297 lines
12 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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 readonly int _runBase;
|
|
|
|
static IngresosBrutosRepositoryTests()
|
|
{
|
|
var bytes = Guid.NewGuid().ToByteArray();
|
|
_runBase = (int)(BitConverter.ToUInt32(bytes, 0) % 500_000u);
|
|
}
|
|
|
|
private static int _testCounter = 0;
|
|
|
|
private static DateOnly NextUniqueDate()
|
|
{
|
|
var counter = Interlocked.Increment(ref _testCounter);
|
|
// Base year 2091 (different from TipoDeIvaRepositoryTests which uses 2090).
|
|
return new DateOnly(2091, 1, 1).AddDays(_runBase + 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<DuplicateProvinciaException>();
|
|
}
|
|
|
|
// ── 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);
|
|
}
|
|
}
|