feat(infrastructure): ProductRepository + ProductQueryRepository, DI swap activates guard (PRD-002)

This commit is contained in:
2026-04-19 13:10:21 -03:00
parent bb455be745
commit 8c9a50504d
6 changed files with 361 additions and 7 deletions

View File

@@ -0,0 +1,120 @@
using Dapper;
using FluentAssertions;
using SIGCM2.Infrastructure.Persistence;
using SIGCM2.TestSupport;
namespace SIGCM2.Application.Tests.Products.Repository;
/// <summary>
/// PRD-002 — Integration tests for ProductQueryRepository against SIGCM2_Test_App.
/// These tests verify the real Dapper implementation replaces NullProductQueryRepository.
/// </summary>
[Collection("Database")]
public class ProductQueryRepositoryTests : IAsyncLifetime
{
private readonly SqlTestFixture _db;
private ProductQueryRepository _repository = null!;
public ProductQueryRepositoryTests(SqlTestFixture db)
{
_db = db;
}
public async Task InitializeAsync()
{
await _db.ResetAndSeedAsync();
var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb);
_repository = new ProductQueryRepository(factory);
}
public Task DisposeAsync() => Task.CompletedTask;
// ── ExistsActiveByProductTypeAsync ───────────────────────────────────────
[Fact]
public async Task ExistsActiveByProductTypeAsync_NoProducts_ReturnsFalse()
{
var result = await _repository.ExistsActiveByProductTypeAsync(productTypeId: 999);
result.Should().BeFalse();
}
[Fact]
public async Task ExistsActiveByProductTypeAsync_WithActiveProduct_ReturnsTrue()
{
// Arrange: insert a ProductType and an active Product referencing it
var (medioId, productTypeId) = await InsertMedioAndProductTypeAsync();
await InsertActiveProductAsync(medioId, productTypeId);
var result = await _repository.ExistsActiveByProductTypeAsync(productTypeId);
result.Should().BeTrue();
}
[Fact]
public async Task ExistsActiveByProductTypeAsync_WithOnlyInactiveProduct_ReturnsFalse()
{
// Arrange: insert an inactive product
var (medioId, productTypeId) = await InsertMedioAndProductTypeAsync();
await InsertInactiveProductAsync(medioId, productTypeId);
var result = await _repository.ExistsActiveByProductTypeAsync(productTypeId);
result.Should().BeFalse();
}
[Fact]
public async Task ExistsActiveByProductTypeAsync_DifferentProductType_ReturnsFalse()
{
// Arrange: insert active product for productTypeId=A, query for productTypeId=B
var (medioId, productTypeId) = await InsertMedioAndProductTypeAsync();
await InsertActiveProductAsync(medioId, productTypeId);
var otherProductTypeId = productTypeId + 100;
var result = await _repository.ExistsActiveByProductTypeAsync(otherProductTypeId);
result.Should().BeFalse();
}
// ── Helpers ───────────────────────────────────────────────────────────────
private async Task<(int MedioId, int ProductTypeId)> InsertMedioAndProductTypeAsync()
{
await using var conn = new Microsoft.Data.SqlClient.SqlConnection(TestConnectionStrings.AppTestDb);
await conn.OpenAsync();
var medioId = await conn.ExecuteScalarAsync<int>("""
INSERT INTO dbo.Medio (Codigo, Nombre, Tipo, Activo)
OUTPUT INSERTED.Id
VALUES ('TM', 'Test Medio', 1, 1)
""");
var productTypeId = await conn.ExecuteScalarAsync<int>("""
INSERT INTO dbo.ProductType (Nombre, HasDuration, RequiresText, RequiresCategory, IsBundle, AllowImages)
OUTPUT INSERTED.Id
VALUES ('Test Type', 0, 0, 0, 0, 0)
""");
return (medioId, productTypeId);
}
private async Task InsertActiveProductAsync(int medioId, int productTypeId)
{
await using var conn = new Microsoft.Data.SqlClient.SqlConnection(TestConnectionStrings.AppTestDb);
await conn.OpenAsync();
await conn.ExecuteAsync("""
INSERT INTO dbo.Product (Nombre, MedioId, ProductTypeId, BasePrice, IsActive, FechaCreacion)
VALUES ('Producto Activo', @MedioId, @ProductTypeId, 100, 1, SYSUTCDATETIME())
""", new { MedioId = medioId, ProductTypeId = productTypeId });
}
private async Task InsertInactiveProductAsync(int medioId, int productTypeId)
{
await using var conn = new Microsoft.Data.SqlClient.SqlConnection(TestConnectionStrings.AppTestDb);
await conn.OpenAsync();
await conn.ExecuteAsync("""
INSERT INTO dbo.Product (Nombre, MedioId, ProductTypeId, BasePrice, IsActive, FechaCreacion)
VALUES ('Producto Inactivo', @MedioId, @ProductTypeId, 100, 0, SYSUTCDATETIME())
""", new { MedioId = medioId, ProductTypeId = productTypeId });
}
}