using FluentAssertions; using foodmarket.Api.Background; using foodmarket.Domain.Inventory; using foodmarket.Domain.Platform; using foodmarket.Infrastructure.Persistence; using foodmarket.UnitTests.Support; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace foodmarket.UnitTests; /// Юнит-тест: HousekeepingJobs.PruneStockMovements удаляет только /// движения старше N дней (по умолчанию 730). Stock-инвариант не проверяем — /// это межтенантная задача очистки далёкой истории, документы за этот горизонт /// в норме закрыты. public class HousekeepingJobsTests { private static IConfiguration CfgWith(int stockDays = 730, int auditDays = 90) => new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["Hangfire:Retention:StockMovementDays"] = stockDays.ToString(), ["Hangfire:Retention:AuditLogDays"] = auditDays.ToString(), }).Build(); [Fact] public async Task PruneStockMovements_deletes_only_old_rows() { using var sqlite = new SqliteDb(foreignKeys: false); var orgId = Guid.NewGuid(); var tenant = new FakeTenantContext { OrganizationId = orgId }; using (var db = sqlite.Create(tenant)) { db.StockMovements.AddRange( new StockMovement { OrganizationId = orgId, ProductId = Guid.NewGuid(), StoreId = Guid.NewGuid(), Quantity = 1m, Type = MovementType.Supply, DocumentType = "supply", OccurredAt = DateTime.UtcNow.AddYears(-3), // старая }, new StockMovement { OrganizationId = orgId, ProductId = Guid.NewGuid(), StoreId = Guid.NewGuid(), Quantity = 1m, Type = MovementType.Supply, DocumentType = "supply", OccurredAt = DateTime.UtcNow.AddDays(-30), // свежая }); await db.SaveChangesAsync(); } int deleted; using (var db = sqlite.Create(tenant)) { var jobs = new HousekeepingJobs(db, CfgWith(stockDays: 730), NullLogger.Instance); deleted = await jobs.PruneStockMovementsAsync(); } deleted.Should().Be(1); using (var db = sqlite.Create(tenant)) { (await db.StockMovements.IgnoreQueryFilters().CountAsync()).Should().Be(1); } } }