using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using NSubstitute; using SIGCM2.Application.Audit; using SIGCM2.Infrastructure.Audit.Jobs; using SIGCM2.Infrastructure.Persistence; namespace SIGCM2.Application.Tests.Infrastructure; /// /// SUITE-BE-JOBS — UDT-011 follow-up (issue #24) /// Verifies that the 3 Quartz audit jobs accept TimeProvider via constructor injection /// and use the injected clock rather than DateTime.UtcNow inline. /// /// Tests are construction-level: they confirm the DI contract is satisfied. /// Full Execute() tests require a live SQL connection and are out of scope. /// public sealed class AuditJobsTimeProviderTests { private static SqlConnectionFactory DummyFactory() => new("Server=.;Database=dummy;Integrated Security=true;"); // ───────────────────────────────────────────────────────────────────────── // AuditIntegrityCheckJob // ───────────────────────────────────────────────────────────────────────── [Fact] public void AuditIntegrityCheckJob_AcceptsFakeTimeProvider_NoException() { var fake = new FakeTimeProvider(); fake.SetUtcNow(new DateTimeOffset(2026, 4, 18, 1, 0, 0, TimeSpan.Zero)); var security = Substitute.For(); var job = new AuditIntegrityCheckJob( DummyFactory(), security, NullLogger.Instance, fake); Assert.NotNull(job); } [Fact] public void AuditIntegrityCheckJob_FakeTimeProvider_ReturnsConfiguredUtcNow() { // Arrange: pin a specific UTC instant var expectedUtc = new DateTimeOffset(2026, 1, 15, 12, 0, 0, TimeSpan.Zero); var fake = new FakeTimeProvider(); fake.SetUtcNow(expectedUtc); var security = Substitute.For(); // Act: the job is constructed with the FakeTimeProvider new AuditIntegrityCheckJob( DummyFactory(), security, NullLogger.Instance, fake); // Assert: the FakeTimeProvider returns the pinned value (not wall clock) fake.GetUtcNow().UtcDateTime.Should().Be(expectedUtc.UtcDateTime); } // ───────────────────────────────────────────────────────────────────────── // AuditPartitionManagerJob // ───────────────────────────────────────────────────────────────────────── [Fact] public void AuditPartitionManagerJob_AcceptsFakeTimeProvider_NoException() { var fake = new FakeTimeProvider(); fake.SetUtcNow(new DateTimeOffset(2026, 4, 18, 2, 0, 0, TimeSpan.Zero)); var job = new AuditPartitionManagerJob( DummyFactory(), NullLogger.Instance, fake); Assert.NotNull(job); } [Fact] public void AuditPartitionManagerJob_FakeTimeProvider_ReturnsConfiguredUtcNow() { // Arrange: pin to 2026-04-01 00:00 UTC (day 1 of month — typical trigger time) var expectedUtc = new DateTimeOffset(2026, 4, 1, 2, 0, 0, TimeSpan.Zero); var fake = new FakeTimeProvider(); fake.SetUtcNow(expectedUtc); // Act new AuditPartitionManagerJob( DummyFactory(), NullLogger.Instance, fake); // Assert: pinned clock is the one the job would use for partition target var now = fake.GetUtcNow().UtcDateTime; var expectedTarget = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc).AddMonths(2); expectedTarget.Should().Be(new DateTime(2026, 6, 1, 0, 0, 0, DateTimeKind.Utc)); } // ───────────────────────────────────────────────────────────────────────── // AuditRetentionEnforcerJob // ───────────────────────────────────────────────────────────────────────── [Fact] public void AuditRetentionEnforcerJob_AcceptsFakeTimeProvider_NoException() { var fake = new FakeTimeProvider(); fake.SetUtcNow(new DateTimeOffset(2026, 1, 1, 3, 0, 0, TimeSpan.Zero)); var job = new AuditRetentionEnforcerJob( DummyFactory(), NullLogger.Instance, fake); Assert.NotNull(job); } [Fact] public void AuditRetentionEnforcerJob_FakeTimeProvider_CutoffDatesUsePinnedClock() { // Arrange: pin to Jan 1, 2026 — retention cutoffs are 2016 and 2021 var pinnedUtc = new DateTimeOffset(2026, 1, 1, 3, 0, 0, TimeSpan.Zero); var fake = new FakeTimeProvider(); fake.SetUtcNow(pinnedUtc); // Act new AuditRetentionEnforcerJob( DummyFactory(), NullLogger.Instance, fake); // Assert: pinned clock yields deterministic cutoffs (not wall-clock-dependent) var now = fake.GetUtcNow().UtcDateTime; now.AddYears(-10).Should().Be(new DateTime(2016, 1, 1, 3, 0, 0, DateTimeKind.Utc)); now.AddYears(-5).Should().Be(new DateTime(2021, 1, 1, 3, 0, 0, DateTimeKind.Utc)); } }