using Microsoft.Extensions.Time.Testing; using Xunit.Abstractions; namespace Newsletter.Clock; public sealed class ClockTests(ITestOutputHelper output) { [Fact] public void Invitation_expires_at_the_boundary() { var start = new DateTimeOffset(2026, 9, 26, 10, 0, 0, TimeSpan.Zero); var clock = new FakeTimeProvider(start); var policy = new InvitationPolicy(clock); var expiresAt = start.AddMinutes(5); clock.Advance(TimeSpan.FromMinutes(5) - TimeSpan.FromTicks(1)); Assert.True(policy.CanAccept(expiresAt)); output.WriteLine("One tick before expiry: accepted"); clock.Advance(TimeSpan.FromTicks(1)); Assert.False(policy.CanAccept(expiresAt)); output.WriteLine("Exactly at expiry: rejected"); clock.Advance(TimeSpan.FromTicks(1)); Assert.False(policy.CanAccept(expiresAt)); output.WriteLine("One tick after expiry: rejected"); } [Fact] public async Task Advance_completes_a_provider_backed_delay() { var clock = new FakeTimeProvider(); Task delay = Task.Delay(TimeSpan.FromMinutes(5), clock); Assert.False(delay.IsCompleted); clock.Advance(TimeSpan.FromMinutes(5)); await delay.WaitAsync(TimeSpan.FromSeconds(5)); Assert.True(delay.IsCompletedSuccessfully); output.WriteLine("Five-minute delay: completed after Advance"); } [Fact] public async Task Advance_does_not_complete_a_system_delay() { var clock = new FakeTimeProvider(); using var cancellation = new CancellationTokenSource(); Task delay = Task.Delay(TimeSpan.FromMinutes(5), cancellation.Token); clock.Advance(TimeSpan.FromMinutes(5)); Assert.False(delay.IsCompleted); output.WriteLine("System delay: still waiting after Advance"); cancellation.Cancel(); await Assert.ThrowsAnyAsync(() => delay); } [Fact] public async Task Provider_backed_timeout_cancels_work_at_its_deadline() { var clock = new FakeTimeProvider(); using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(1), clock); Task work = Task.Delay(TimeSpan.FromHours(1), clock, timeout.Token); clock.Advance(TimeSpan.FromMinutes(1) - TimeSpan.FromTicks(1)); Assert.False(timeout.IsCancellationRequested); Assert.False(work.IsCompleted); output.WriteLine("One tick before timeout: work pending"); clock.Advance(TimeSpan.FromTicks(1)); await Assert.ThrowsAnyAsync( () => work.WaitAsync(TimeSpan.FromSeconds(5))); Assert.True(timeout.IsCancellationRequested); output.WriteLine("Exactly at timeout: work canceled"); } [Fact] public void Advance_does_not_trigger_a_system_timeout() { var clock = new FakeTimeProvider(); using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(1)); clock.Advance(TimeSpan.FromMinutes(1)); Assert.False(timeout.IsCancellationRequested); output.WriteLine("System timeout: still pending after Advance"); } }