using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit; using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Maintenance; namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests.Maintenance; /// Tests for the opt-in config-audit retention purge (review 2026-10-21 08/G1). public class ConfigAuditPurgeServiceTests : IDisposable { private readonly ScadaBridgeDbContext _context = SqliteTestHelper.CreateInMemoryContext(); public void Dispose() { _context.Database.CloseConnection(); _context.Dispose(); } private ConfigAuditPurgeService Create(ConfigAuditOptions opts) { var services = new ServiceCollection(); services.AddSingleton(_context); return new ConfigAuditPurgeService( services.BuildServiceProvider().GetRequiredService(), Options.Create(opts), TimeProvider.System, NullLogger.Instance); } private async Task SeedAsync(int oldCount, int recentCount) { var now = DateTimeOffset.UtcNow; for (var i = 0; i < oldCount; i++) _context.AuditLogEntries.Add(new AuditLogEntry("u", "Create", "Template", $"o{i}", $"Old{i}") { Timestamp = now.AddDays(-100).AddMinutes(i) }); for (var i = 0; i < recentCount; i++) _context.AuditLogEntries.Add(new AuditLogEntry("u", "Create", "Template", $"r{i}", $"New{i}") { Timestamp = now.AddDays(-1) }); await _context.SaveChangesAsync(); } [Fact] public async Task RetentionZero_DeletesNothing() { await SeedAsync(5, 2); var deleted = await Create(new ConfigAuditOptions { RetentionDays = 0 }).PurgeOnceAsync(); Assert.Equal(0, deleted); Assert.Equal(7, _context.AuditLogEntries.Count()); } [Fact] public async Task Retention_DeletesOnlyOlderThanWindow_AcrossBatches() { await SeedAsync(25, 3); var deleted = await Create(new ConfigAuditOptions { RetentionDays = 30, PurgeBatchSize = 10 }).PurgeOnceAsync(); Assert.Equal(25, deleted); Assert.Equal(3, _context.AuditLogEntries.Count()); } [Fact] public async Task Retention_MaxBatchesPerTick_TimeSlicesTheBacklog() { await SeedAsync(25, 0); var svc = Create(new ConfigAuditOptions { RetentionDays = 30, PurgeBatchSize = 10, MaxBatchesPerTick = 2 }); Assert.Equal(20, await svc.PurgeOnceAsync()); Assert.Equal(5, await svc.PurgeOnceAsync()); } [Fact] public void Validator_RejectsNegativeRetention_AcceptsZero() { var v = new ConfigAuditOptionsValidator(); Assert.True(v.Validate(null, new ConfigAuditOptions { RetentionDays = 0 }).Succeeded); Assert.True(v.Validate(null, new ConfigAuditOptions { RetentionDays = 90 }).Succeeded); Assert.False(v.Validate(null, new ConfigAuditOptions { RetentionDays = -1 }).Succeeded); Assert.False(v.Validate(null, new ConfigAuditOptions { PurgeBatchSize = 0 }).Succeeded); } }