test(integration): pagination edge cases (prd-003-prices-pagination)

This commit is contained in:
2026-04-19 20:01:09 -03:00
parent 34b07a1d55
commit e997409e95
2 changed files with 167 additions and 0 deletions

View File

@@ -440,6 +440,77 @@ public class ProductPriceRepositoryIntegrationTests : IAsyncLifetime
page2.Total.Should().Be(6);
}
// §B3.2 — 30 prices: page 1 and page 2 (pageSize=10) are fully disjoint,
// union = 20 distinct items, each page is ordered DESC by PriceValidFrom.
[Fact]
public async Task GetByProductIdAsync_ThirtyPrices_TwoPages_AreDisjointAndOrderedDesc()
{
await using var seedConn = new SqlConnection(TestConnectionStrings.AppTestDb);
await seedConn.OpenAsync();
// Insert 30 prices directly (bypass SP to avoid forward-only complexity).
// All rows get an explicit PriceValidTo so no unique-index violation.
// PVF: 2026-01-01 to 2026-01-30 (ascending), all closed (PVT = PVF + 1 day).
// Except row 30 which has PVT = NULL (the single active row).
for (var i = 1; i <= 30; i++)
{
DateTime? pvt = i < 30
? (DateTime?)new DateTime(2026, 1, i + 1)
: null;
await seedConn.ExecuteAsync("""
INSERT INTO dbo.ProductPrices (ProductId, Price, PriceValidFrom, PriceValidTo, FechaCreacion)
VALUES (@ProductId, @Price, @PriceValidFrom, @PriceValidTo, SYSUTCDATETIME())
""",
new
{
ProductId = _defaultProductId,
Price = (decimal)(i * 10),
PriceValidFrom = new DateTime(2026, 1, i),
PriceValidTo = pvt
});
}
var repo = BuildRepository();
var page1 = await repo.GetByProductIdAsync(_defaultProductId, page: 1, pageSize: 10);
var page2 = await repo.GetByProductIdAsync(_defaultProductId, page: 2, pageSize: 10);
// Both pages must reflect the full dataset
page1.Total.Should().Be(30);
page2.Total.Should().Be(30);
// Each page must have exactly 10 items
page1.Items.Should().HaveCount(10);
page2.Items.Should().HaveCount(10);
// Page meta
page1.Page.Should().Be(1);
page1.PageSize.Should().Be(10);
page2.Page.Should().Be(2);
page2.PageSize.Should().Be(10);
// The two ID sets must be fully disjoint
var ids1 = page1.Items.Select(p => p.PriceValidFrom).ToHashSet();
var ids2 = page2.Items.Select(p => p.PriceValidFrom).ToHashSet();
ids1.Intersect(ids2).Should().BeEmpty("pages must not share any items");
// Union covers exactly 20 distinct items
ids1.Union(ids2).Should().HaveCount(20, "union of two pages of 10 must yield 20 distinct items");
// Page 1 must be ordered DESC — first item is PVF Jan 30 (most recent)
page1.Items[0].PriceValidFrom.Should().Be(new DateOnly(2026, 1, 30),
"page 1 rank-1 must be the most recent price (DESC)");
page1.Items[9].PriceValidFrom.Should().Be(new DateOnly(2026, 1, 21),
"page 1 rank-10 must be Jan 21");
// Page 2 must continue DESC — first item is PVF Jan 20
page2.Items[0].PriceValidFrom.Should().Be(new DateOnly(2026, 1, 20),
"page 2 rank-1 must be Jan 20 (rank 11 globally in DESC order)");
page2.Items[9].PriceValidFrom.Should().Be(new DateOnly(2026, 1, 11),
"page 2 rank-10 must be Jan 11");
}
// §REQ-4.4 — GetActiveAsync: exact boundary PriceValidFrom = query date → returns row
[Fact]
public async Task GetActiveAsync_ExactBoundaryPvf_ReturnsRow()