feat(infrastructure): ProductTypeRepository Dapper + DI wiring (PRD-001)
CRUD + paginado con filtros sobre dbo.ProductType; history temporal verificada en tests. 11 integration tests nuevos, suite total 935 GREEN.
This commit is contained in:
@@ -39,6 +39,7 @@ public static class DependencyInjection
|
|||||||
services.AddScoped<ITipoDeIvaRepository, TipoDeIvaRepository>();
|
services.AddScoped<ITipoDeIvaRepository, TipoDeIvaRepository>();
|
||||||
services.AddScoped<IIngresosBrutosRepository, IngresosBrutosRepository>();
|
services.AddScoped<IIngresosBrutosRepository, IngresosBrutosRepository>();
|
||||||
services.AddScoped<IRubroRepository, RubroRepository>();
|
services.AddScoped<IRubroRepository, RubroRepository>();
|
||||||
|
services.AddScoped<IProductTypeRepository, ProductTypeRepository>();
|
||||||
|
|
||||||
// JWT Options — bound lazily via IOptions so tests can override via ConfigureWebHost
|
// JWT Options — bound lazily via IOptions so tests can override via ConfigureWebHost
|
||||||
services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
|
services.Configure<JwtOptions>(configuration.GetSection("Jwt"));
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
using Dapper;
|
||||||
|
using SIGCM2.Application.Abstractions.Persistence;
|
||||||
|
using SIGCM2.Application.Common;
|
||||||
|
using SIGCM2.Domain.Entities;
|
||||||
|
|
||||||
|
namespace SIGCM2.Infrastructure.Persistence;
|
||||||
|
|
||||||
|
public sealed class ProductTypeRepository : IProductTypeRepository
|
||||||
|
{
|
||||||
|
private readonly SqlConnectionFactory _factory;
|
||||||
|
|
||||||
|
public ProductTypeRepository(SqlConnectionFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> AddAsync(ProductType productType, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// DF handles: IsActive (1), FechaCreacion (SYSUTCDATETIME()).
|
||||||
|
const string sql = """
|
||||||
|
INSERT INTO dbo.ProductType (
|
||||||
|
Nombre, HasDuration, RequiresText, RequiresCategory, IsBundle,
|
||||||
|
AllowImages, MaxImages, MaxImageSizeMB, MaxImageWidth, MaxImageHeight
|
||||||
|
)
|
||||||
|
OUTPUT INSERTED.Id
|
||||||
|
VALUES (
|
||||||
|
@Nombre, @HasDuration, @RequiresText, @RequiresCategory, @IsBundle,
|
||||||
|
@AllowImages, @MaxImages, @MaxImageSizeMB, @MaxImageWidth, @MaxImageHeight
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
|
||||||
|
await using var connection = _factory.CreateConnection();
|
||||||
|
await connection.OpenAsync(ct);
|
||||||
|
|
||||||
|
return await connection.ExecuteScalarAsync<int>(sql, new
|
||||||
|
{
|
||||||
|
productType.Nombre,
|
||||||
|
HasDuration = productType.HasDuration ? 1 : 0,
|
||||||
|
RequiresText = productType.RequiresText ? 1 : 0,
|
||||||
|
RequiresCategory = productType.RequiresCategory ? 1 : 0,
|
||||||
|
IsBundle = productType.IsBundle ? 1 : 0,
|
||||||
|
AllowImages = productType.AllowImages ? 1 : 0,
|
||||||
|
productType.MaxImages,
|
||||||
|
productType.MaxImageSizeMB,
|
||||||
|
productType.MaxImageWidth,
|
||||||
|
productType.MaxImageHeight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProductType?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
const string sql = """
|
||||||
|
SELECT Id, Nombre, HasDuration, RequiresText, RequiresCategory, IsBundle,
|
||||||
|
AllowImages, MaxImages, MaxImageSizeMB, MaxImageWidth, MaxImageHeight,
|
||||||
|
IsActive, FechaCreacion, FechaModificacion
|
||||||
|
FROM dbo.ProductType
|
||||||
|
WHERE Id = @Id
|
||||||
|
""";
|
||||||
|
|
||||||
|
await using var connection = _factory.CreateConnection();
|
||||||
|
await connection.OpenAsync(ct);
|
||||||
|
|
||||||
|
var row = await connection.QuerySingleOrDefaultAsync<ProductTypeRow>(sql, new { Id = id });
|
||||||
|
return row is null ? null : MapRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PagedResult<ProductType>> GetPagedAsync(
|
||||||
|
ProductTypesQuery query,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// Build the WHERE clause dynamically.
|
||||||
|
var conditions = new List<string>();
|
||||||
|
if (query.Activo.HasValue)
|
||||||
|
conditions.Add("IsActive = @Activo");
|
||||||
|
if (!string.IsNullOrWhiteSpace(query.Search))
|
||||||
|
conditions.Add("Nombre LIKE '%' + @Search + '%'");
|
||||||
|
|
||||||
|
var where = conditions.Count > 0
|
||||||
|
? "WHERE " + string.Join(" AND ", conditions)
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
var countSql = $"SELECT COUNT(1) FROM dbo.ProductType {where}";
|
||||||
|
var dataSql = $"""
|
||||||
|
SELECT Id, Nombre, HasDuration, RequiresText, RequiresCategory, IsBundle,
|
||||||
|
AllowImages, MaxImages, MaxImageSizeMB, MaxImageWidth, MaxImageHeight,
|
||||||
|
IsActive, FechaCreacion, FechaModificacion
|
||||||
|
FROM dbo.ProductType
|
||||||
|
{where}
|
||||||
|
ORDER BY Nombre
|
||||||
|
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY
|
||||||
|
""";
|
||||||
|
|
||||||
|
var offset = (query.Page - 1) * query.PageSize;
|
||||||
|
var parameters = new
|
||||||
|
{
|
||||||
|
Activo = query.Activo.HasValue ? (object)(query.Activo.Value ? 1 : 0) : null,
|
||||||
|
Search = string.IsNullOrWhiteSpace(query.Search) ? null : query.Search,
|
||||||
|
Offset = offset,
|
||||||
|
PageSize = query.PageSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
await using var connection = _factory.CreateConnection();
|
||||||
|
await connection.OpenAsync(ct);
|
||||||
|
|
||||||
|
var total = await connection.ExecuteScalarAsync<int>(countSql, parameters);
|
||||||
|
var rows = await connection.QueryAsync<ProductTypeRow>(dataSql, parameters);
|
||||||
|
var items = rows.Select(MapRow).ToList();
|
||||||
|
|
||||||
|
return new PagedResult<ProductType>(items, query.Page, query.PageSize, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(ProductType productType, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
const string sql = """
|
||||||
|
UPDATE dbo.ProductType
|
||||||
|
SET Nombre = @Nombre,
|
||||||
|
HasDuration = @HasDuration,
|
||||||
|
RequiresText = @RequiresText,
|
||||||
|
RequiresCategory = @RequiresCategory,
|
||||||
|
IsBundle = @IsBundle,
|
||||||
|
AllowImages = @AllowImages,
|
||||||
|
MaxImages = @MaxImages,
|
||||||
|
MaxImageSizeMB = @MaxImageSizeMB,
|
||||||
|
MaxImageWidth = @MaxImageWidth,
|
||||||
|
MaxImageHeight = @MaxImageHeight,
|
||||||
|
IsActive = @IsActive,
|
||||||
|
FechaModificacion = @FechaModificacion
|
||||||
|
WHERE Id = @Id
|
||||||
|
""";
|
||||||
|
|
||||||
|
await using var connection = _factory.CreateConnection();
|
||||||
|
await connection.OpenAsync(ct);
|
||||||
|
|
||||||
|
await connection.ExecuteAsync(sql, new
|
||||||
|
{
|
||||||
|
productType.Nombre,
|
||||||
|
HasDuration = productType.HasDuration ? 1 : 0,
|
||||||
|
RequiresText = productType.RequiresText ? 1 : 0,
|
||||||
|
RequiresCategory = productType.RequiresCategory ? 1 : 0,
|
||||||
|
IsBundle = productType.IsBundle ? 1 : 0,
|
||||||
|
AllowImages = productType.AllowImages ? 1 : 0,
|
||||||
|
productType.MaxImages,
|
||||||
|
productType.MaxImageSizeMB,
|
||||||
|
productType.MaxImageWidth,
|
||||||
|
productType.MaxImageHeight,
|
||||||
|
IsActive = productType.IsActive ? 1 : 0,
|
||||||
|
productType.FechaModificacion,
|
||||||
|
productType.Id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsByNombreAsync(
|
||||||
|
string nombre,
|
||||||
|
int? excludeId = null,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// DB collation is SQL_Latin1_General_CP1_CI_AI on Nombre (CI) — comparison is
|
||||||
|
// already case-insensitive; no need for UPPER(). The filtered unique index
|
||||||
|
// (UQ_ProductType_Nombre_Activo WHERE IsActive=1) aligns with this query.
|
||||||
|
const string sql = """
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM dbo.ProductType
|
||||||
|
WHERE Nombre = @Nombre
|
||||||
|
AND IsActive = 1
|
||||||
|
AND (@ExcludeId IS NULL OR Id <> @ExcludeId)
|
||||||
|
""";
|
||||||
|
|
||||||
|
await using var connection = _factory.CreateConnection();
|
||||||
|
await connection.OpenAsync(ct);
|
||||||
|
|
||||||
|
var count = await connection.ExecuteScalarAsync<int>(sql, new
|
||||||
|
{
|
||||||
|
Nombre = nombre,
|
||||||
|
ExcludeId = excludeId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── mapping ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static ProductType MapRow(ProductTypeRow r)
|
||||||
|
=> new(
|
||||||
|
id: r.Id,
|
||||||
|
nombre: r.Nombre,
|
||||||
|
hasDuration: r.HasDuration,
|
||||||
|
requiresText: r.RequiresText,
|
||||||
|
requiresCategory: r.RequiresCategory,
|
||||||
|
isBundle: r.IsBundle,
|
||||||
|
allowImages: r.AllowImages,
|
||||||
|
maxImages: r.MaxImages,
|
||||||
|
maxImageSizeMB: r.MaxImageSizeMB,
|
||||||
|
maxImageWidth: r.MaxImageWidth,
|
||||||
|
maxImageHeight: r.MaxImageHeight,
|
||||||
|
isActive: r.IsActive,
|
||||||
|
fechaCreacion: r.FechaCreacion,
|
||||||
|
fechaModificacion: r.FechaModificacion);
|
||||||
|
|
||||||
|
private sealed record ProductTypeRow(
|
||||||
|
int Id,
|
||||||
|
string Nombre,
|
||||||
|
bool HasDuration,
|
||||||
|
bool RequiresText,
|
||||||
|
bool RequiresCategory,
|
||||||
|
bool IsBundle,
|
||||||
|
bool AllowImages,
|
||||||
|
int? MaxImages,
|
||||||
|
decimal? MaxImageSizeMB,
|
||||||
|
int? MaxImageWidth,
|
||||||
|
int? MaxImageHeight,
|
||||||
|
bool IsActive,
|
||||||
|
DateTime FechaCreacion,
|
||||||
|
DateTime? FechaModificacion);
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
using Dapper;
|
||||||
|
using FluentAssertions;
|
||||||
|
using SIGCM2.Domain.Entities;
|
||||||
|
using SIGCM2.Infrastructure.Persistence;
|
||||||
|
using SIGCM2.TestSupport;
|
||||||
|
|
||||||
|
namespace SIGCM2.Application.Tests.ProductTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Integration tests for ProductTypeRepository against SIGCM2_Test_App.
|
||||||
|
/// Uses shared SqlTestFixture via [Collection("Database")] — fixture maneja Respawn + seeds.
|
||||||
|
/// Temporal: after UpdateAsync, dbo.ProductType_History MUST have ≥1 row for that Id.
|
||||||
|
/// </summary>
|
||||||
|
[Collection("Database")]
|
||||||
|
public class ProductTypeRepositoryTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly SqlTestFixture _db;
|
||||||
|
private ProductTypeRepository _repository = null!;
|
||||||
|
private TimeProvider _timeProvider = null!;
|
||||||
|
|
||||||
|
public ProductTypeRepositoryTests(SqlTestFixture db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
await _db.ResetAndSeedAsync();
|
||||||
|
|
||||||
|
var factory = new SqlConnectionFactory(TestConnectionStrings.AppTestDb);
|
||||||
|
_repository = new ProductTypeRepository(factory);
|
||||||
|
_timeProvider = TimeProvider.System;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DisposeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
// ── AddAsync + GetByIdAsync roundtrip ─────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddAsync_AndGetById_ReturnsAllFields()
|
||||||
|
{
|
||||||
|
var pt = ProductType.ForCreation(
|
||||||
|
nombre: "Avisos Clasificados",
|
||||||
|
hasDuration: true,
|
||||||
|
requiresText: true,
|
||||||
|
requiresCategory: true,
|
||||||
|
isBundle: false,
|
||||||
|
allowImages: true,
|
||||||
|
maxImages: 5,
|
||||||
|
maxImageSizeMB: 2.5m,
|
||||||
|
maxImageWidth: 800,
|
||||||
|
maxImageHeight: 600,
|
||||||
|
timeProvider: _timeProvider);
|
||||||
|
|
||||||
|
var id = await _repository.AddAsync(pt);
|
||||||
|
var result = await _repository.GetByIdAsync(id);
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Id.Should().Be(id);
|
||||||
|
result.Nombre.Should().Be("Avisos Clasificados");
|
||||||
|
result.HasDuration.Should().BeTrue();
|
||||||
|
result.RequiresText.Should().BeTrue();
|
||||||
|
result.RequiresCategory.Should().BeTrue();
|
||||||
|
result.IsBundle.Should().BeFalse();
|
||||||
|
result.AllowImages.Should().BeTrue();
|
||||||
|
result.MaxImages.Should().Be(5);
|
||||||
|
result.MaxImageSizeMB.Should().Be(2.5m);
|
||||||
|
result.MaxImageWidth.Should().Be(800);
|
||||||
|
result.MaxImageHeight.Should().Be(600);
|
||||||
|
result.IsActive.Should().BeTrue();
|
||||||
|
result.FechaCreacion.Should().BeAfter(DateTime.MinValue);
|
||||||
|
result.FechaModificacion.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddAsync_NoImages_PersistsNullMultimedia()
|
||||||
|
{
|
||||||
|
var pt = ProductType.ForCreation(
|
||||||
|
nombre: "Aviso Simple",
|
||||||
|
hasDuration: false,
|
||||||
|
requiresText: false,
|
||||||
|
requiresCategory: false,
|
||||||
|
isBundle: false,
|
||||||
|
allowImages: false,
|
||||||
|
maxImages: null,
|
||||||
|
maxImageSizeMB: null,
|
||||||
|
maxImageWidth: null,
|
||||||
|
maxImageHeight: null,
|
||||||
|
timeProvider: _timeProvider);
|
||||||
|
|
||||||
|
var id = await _repository.AddAsync(pt);
|
||||||
|
var result = await _repository.GetByIdAsync(id);
|
||||||
|
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.AllowImages.Should().BeFalse();
|
||||||
|
result.MaxImages.Should().BeNull();
|
||||||
|
result.MaxImageSizeMB.Should().BeNull();
|
||||||
|
result.MaxImageWidth.Should().BeNull();
|
||||||
|
result.MaxImageHeight.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetByIdAsync_NonExistent_ReturnsNull()
|
||||||
|
{
|
||||||
|
var result = await _repository.GetByIdAsync(999999);
|
||||||
|
|
||||||
|
result.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ExistsByNombreAsync ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExistsByNombreAsync_ExistingActiveNombre_ReturnsTrue()
|
||||||
|
{
|
||||||
|
var pt = ProductType.ForCreation("Tipo Unico", false, false, false, false, false, null, null, null, null, _timeProvider);
|
||||||
|
await _repository.AddAsync(pt);
|
||||||
|
|
||||||
|
var exists = await _repository.ExistsByNombreAsync("Tipo Unico");
|
||||||
|
|
||||||
|
exists.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExistsByNombreAsync_WithExcludeId_ExcludesSelf()
|
||||||
|
{
|
||||||
|
var pt = ProductType.ForCreation("Auto-Excluido", false, false, false, false, false, null, null, null, null, _timeProvider);
|
||||||
|
var id = await _repository.AddAsync(pt);
|
||||||
|
|
||||||
|
var exists = await _repository.ExistsByNombreAsync("Auto-Excluido", excludeId: id);
|
||||||
|
|
||||||
|
exists.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExistsByNombreAsync_InactiveNombre_ReturnsFalse()
|
||||||
|
{
|
||||||
|
var pt = ProductType.ForCreation("Tipo Inactivo", false, false, false, false, false, null, null, null, null, _timeProvider);
|
||||||
|
var id = await _repository.AddAsync(pt);
|
||||||
|
var entity = await _repository.GetByIdAsync(id);
|
||||||
|
await _repository.UpdateAsync(entity!.WithDeactivated(_timeProvider));
|
||||||
|
|
||||||
|
var exists = await _repository.ExistsByNombreAsync("Tipo Inactivo");
|
||||||
|
|
||||||
|
exists.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── UpdateAsync ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_PersistsChanges()
|
||||||
|
{
|
||||||
|
var pt = ProductType.ForCreation("Original", false, false, false, false, false, null, null, null, null, _timeProvider);
|
||||||
|
var id = await _repository.AddAsync(pt);
|
||||||
|
var loaded = await _repository.GetByIdAsync(id);
|
||||||
|
|
||||||
|
var updated = loaded!
|
||||||
|
.WithRenamed("Renombrado", _timeProvider)
|
||||||
|
.WithUpdatedFlags(true, true, false, false, _timeProvider);
|
||||||
|
await _repository.UpdateAsync(updated);
|
||||||
|
|
||||||
|
var result = await _repository.GetByIdAsync(id);
|
||||||
|
result!.Nombre.Should().Be("Renombrado");
|
||||||
|
result.HasDuration.Should().BeTrue();
|
||||||
|
result.RequiresText.Should().BeTrue();
|
||||||
|
result.FechaModificacion.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAsync_DeactivatesAndRecordsHistory()
|
||||||
|
{
|
||||||
|
var pt = ProductType.ForCreation("Para Baja", false, false, false, false, false, null, null, null, null, _timeProvider);
|
||||||
|
var id = await _repository.AddAsync(pt);
|
||||||
|
var loaded = await _repository.GetByIdAsync(id);
|
||||||
|
|
||||||
|
await _repository.UpdateAsync(loaded!.WithDeactivated(_timeProvider));
|
||||||
|
|
||||||
|
var result = await _repository.GetByIdAsync(id);
|
||||||
|
result!.IsActive.Should().BeFalse();
|
||||||
|
|
||||||
|
// Verify temporal history has at least 1 row
|
||||||
|
var historyCount = await _db.Connection.ExecuteScalarAsync<int>(
|
||||||
|
"SELECT COUNT(1) FROM dbo.ProductType_History WHERE Id = @Id", new { Id = id });
|
||||||
|
historyCount.Should().BeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── GetPagedAsync ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPagedAsync_FiltersActiveByDefault()
|
||||||
|
{
|
||||||
|
var active = ProductType.ForCreation("Activo Paginado", false, false, false, false, false, null, null, null, null, _timeProvider);
|
||||||
|
var inactive = ProductType.ForCreation("Inactivo Paginado", false, false, false, false, false, null, null, null, null, _timeProvider);
|
||||||
|
|
||||||
|
await _repository.AddAsync(active);
|
||||||
|
var inactiveId = await _repository.AddAsync(inactive);
|
||||||
|
var inactiveLoaded = await _repository.GetByIdAsync(inactiveId);
|
||||||
|
await _repository.UpdateAsync(inactiveLoaded!.WithDeactivated(_timeProvider));
|
||||||
|
|
||||||
|
var query = new SIGCM2.Application.Common.ProductTypesQuery(Page: 1, PageSize: 50, Activo: true);
|
||||||
|
var result = await _repository.GetPagedAsync(query);
|
||||||
|
|
||||||
|
result.Items.Should().Contain(x => x.Nombre == "Activo Paginado");
|
||||||
|
result.Items.Should().NotContain(x => x.Nombre == "Inactivo Paginado");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPagedAsync_SearchFilter_FiltersCorrectly()
|
||||||
|
{
|
||||||
|
await _repository.AddAsync(ProductType.ForCreation("Buscar ABC", false, false, false, false, false, null, null, null, null, _timeProvider));
|
||||||
|
await _repository.AddAsync(ProductType.ForCreation("Otro Tipo", false, false, false, false, false, null, null, null, null, _timeProvider));
|
||||||
|
|
||||||
|
var query = new SIGCM2.Application.Common.ProductTypesQuery(Page: 1, PageSize: 50, Activo: null, Search: "ABC");
|
||||||
|
var result = await _repository.GetPagedAsync(query);
|
||||||
|
|
||||||
|
result.Items.Should().Contain(x => x.Nombre == "Buscar ABC");
|
||||||
|
result.Items.Should().NotContain(x => x.Nombre == "Otro Tipo");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPagedAsync_Pagination_RespectsPageSize()
|
||||||
|
{
|
||||||
|
for (var i = 1; i <= 5; i++)
|
||||||
|
await _repository.AddAsync(ProductType.ForCreation($"Paginado {i:D2}", false, false, false, false, false, null, null, null, null, _timeProvider));
|
||||||
|
|
||||||
|
var query = new SIGCM2.Application.Common.ProductTypesQuery(Page: 1, PageSize: 3, Activo: null);
|
||||||
|
var result = await _repository.GetPagedAsync(query);
|
||||||
|
|
||||||
|
result.Items.Should().HaveCount(3);
|
||||||
|
result.Total.Should().BeGreaterThanOrEqualTo(5);
|
||||||
|
result.Page.Should().Be(1);
|
||||||
|
result.PageSize.Should().Be(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user