diff --git a/source/EasyWay/Clock.cs b/source/EasyWay/Clock.cs index 28a67e3..2b4aa04 100644 --- a/source/EasyWay/Clock.cs +++ b/source/EasyWay/Clock.cs @@ -2,33 +2,12 @@ namespace EasyWay { - public static class Clock + public sealed class Clock { - [ThreadStatic] private static TimeSpan? _differenceBetweenMoments; + public TimeProvider TimeProvider => InternalClock.TimeProvider; - public static DateTime UtcNow - { - get - { - if (_differenceBetweenMoments.HasValue) - { - return DateTime.UtcNow.Add(_differenceBetweenMoments.Value); - } - - return DateTime.UtcNow; - } - } - - internal static void Set(DateTime customDateTime) - { - if (customDateTime.Kind != DateTimeKind.Utc) - { - throw new CustomDateTimeMustBeUtcException(); - } - - _differenceBetweenMoments = customDateTime - DateTime.UtcNow; - } - - internal static void Reset() => _differenceBetweenMoments = null; + public DateTime UtcNow => InternalClock.UtcNow; + + internal Clock() { } } } diff --git a/source/EasyWay/DomainEvent.cs b/source/EasyWay/DomainEvent.cs index 6167cd9..916fc35 100644 --- a/source/EasyWay/DomainEvent.cs +++ b/source/EasyWay/DomainEvent.cs @@ -1,4 +1,5 @@ -using EasyWay.Internals.GuidGenerators; +using EasyWay.Internals.Clocks; +using EasyWay.Internals.GuidGenerators; namespace EasyWay { @@ -14,7 +15,7 @@ public abstract class DomainEvent protected DomainEvent() { EventId = GuidGenerator.New; - OccurrenceOn = Clock.UtcNow; + OccurrenceOn = InternalClock.UtcNow; } } } diff --git a/source/EasyWay/Internals/Clocks/CustomDateTimeMustBeUtcException.cs b/source/EasyWay/Internals/Clocks/CustomDateTimeMustBeUtcException.cs deleted file mode 100644 index 57cf230..0000000 --- a/source/EasyWay/Internals/Clocks/CustomDateTimeMustBeUtcException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace EasyWay.Internals.Clocks -{ - internal sealed class CustomDateTimeMustBeUtcException : EasyWayException - { - internal CustomDateTimeMustBeUtcException() { } - } -} - diff --git a/source/EasyWay/Internals/Clocks/Extensions.cs b/source/EasyWay/Internals/Clocks/Extensions.cs new file mode 100644 index 0000000..ff55a35 --- /dev/null +++ b/source/EasyWay/Internals/Clocks/Extensions.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace EasyWay.Internals.Clocks +{ + internal static class Extensions + { + internal static IServiceCollection AddClocks(this IServiceCollection services) + { + var clock = new Clock(); + + services.AddSingleton(clock); + services.AddSingleton(clock.TimeProvider); + + return services; + } + } +} diff --git a/source/EasyWay/Internals/Clocks/InternalClock.cs b/source/EasyWay/Internals/Clocks/InternalClock.cs new file mode 100644 index 0000000..c43d4be --- /dev/null +++ b/source/EasyWay/Internals/Clocks/InternalClock.cs @@ -0,0 +1,15 @@ +namespace EasyWay.Internals.Clocks +{ + internal static class InternalClock + { + [ThreadStatic] private static TimeProvider? _testTimeProvider; + + private static TimeProvider _timeProvider = TimeProvider.System; + + internal static TimeProvider TimeProvider => _testTimeProvider is null ? _timeProvider : _testTimeProvider; + + internal static DateTime UtcNow => TimeProvider.GetUtcNow().UtcDateTime; + + internal static void Test(TimeProvider timeProvider) => _testTimeProvider = timeProvider; + } +} diff --git a/source/EasyWay/Internals/Extensions.cs b/source/EasyWay/Internals/Extensions.cs index a6a05c1..5508031 100644 --- a/source/EasyWay/Internals/Extensions.cs +++ b/source/EasyWay/Internals/Extensions.cs @@ -1,4 +1,5 @@ using EasyWay.Internals.AggregateRoots; +using EasyWay.Internals.Clocks; using EasyWay.Internals.Commands; using EasyWay.Internals.Contexts; using EasyWay.Internals.DomainEvents; @@ -21,6 +22,7 @@ internal static void AddEasyWay( IEnumerable assemblies) { services + .AddClocks() .AddContexts() .AddAggregateRoots() .AddCommands(moduleType, assemblies) diff --git a/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs b/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs index cdeeca0..23718ed 100644 --- a/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs +++ b/source/EasyWay/Internals/GuidGenerators/GuidGenerator.cs @@ -1,4 +1,6 @@ -namespace EasyWay.Internals.GuidGenerators +using EasyWay.Internals.Clocks; + +namespace EasyWay.Internals.GuidGenerators { internal static class GuidGenerator { @@ -10,6 +12,6 @@ internal static class GuidGenerator internal static void Reset() => _customId = null; - private static Guid Create() => Guid.CreateVersion7(Clock.UtcNow); + private static Guid Create() => Guid.CreateVersion7(InternalClock.UtcNow); } } diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index 49bde6b..f18cbea 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -4,6 +4,7 @@ false + diff --git a/tests/EasyWay.Tests/Clocks/ClockTests.cs b/tests/EasyWay.Tests/Clocks/ClockTests.cs deleted file mode 100644 index 4801e31..0000000 --- a/tests/EasyWay.Tests/Clocks/ClockTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using EasyWay.Internals.Clocks; - -namespace EasyWay.Tests.Clocks -{ - public sealed class ClockTests - { - [Fact(DisplayName = $"{nameof(Clock)} should return date time UTC now")] - public void ClockShouldReturnUtcNow() - { - // Arrange - var expectedUtcNow = DateTime.UtcNow; - TimeSpan precision = TimeSpan.FromMilliseconds(50); - - // Act - var utcNow = Clock.UtcNow; - - // Assert - Assert.Equal(DateTimeKind.Utc, utcNow.Kind); - Assert.Equal(expectedUtcNow, utcNow, precision); - } - - [Fact(DisplayName = $"{nameof(Clock)} should throw exception when set date time is not UTC")] - public void ClockShouldThrowExceptionWhenSetDateTimeIsNotUtc() - { - // Arrange - var unspecifiedDateTime = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Unspecified); - - // Assert - Assert.Throws(() => - { - // Act - Clock.Set(unspecifiedDateTime); - }); - } - - [Fact(DisplayName = $"{nameof(Clock)} should return the expected date time when {nameof(Clock.Set)}")] - public void ClockShouldReturnExceptedDateTimeWhenSet() - { - // Arrange - var expectedUtc = new DateTime(2024,1,1,0,0,0, DateTimeKind.Utc); - TimeSpan precision = TimeSpan.FromMilliseconds(50); - - Clock.Set(expectedUtc); - - // Act - var utcNow = Clock.UtcNow; - - // Assert - Assert.Equal(DateTimeKind.Utc, utcNow.Kind); - Assert.True(expectedUtc != utcNow); - Assert.Equal(expectedUtc, utcNow, precision); - - Clock.Reset(); - } - - [Fact(DisplayName = $"{nameof(Clock)} should return the current UTC date time when {nameof(Clock.Set)} and then {nameof(Clock.Reset)}")] - public void ClockShouldReturnExceptedDate() - { - // Arrange - var expectedUtc = DateTime.UtcNow; - - TimeSpan precision = TimeSpan.FromMilliseconds(50); - - Clock.Set(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)); - - // Act - Clock.Reset(); - - // Assert - Assert.Equal(DateTimeKind.Utc, Clock.UtcNow.Kind); - Assert.Equal(expectedUtc, Clock.UtcNow, precision); - } - } -} diff --git a/tests/EasyWay.Tests/DomainEvents/DomainEventTests.cs b/tests/EasyWay.Tests/DomainEvents/DomainEventTests.cs index 6937bd9..b5af5b7 100644 --- a/tests/EasyWay.Tests/DomainEvents/DomainEventTests.cs +++ b/tests/EasyWay.Tests/DomainEvents/DomainEventTests.cs @@ -1,4 +1,7 @@ -namespace EasyWay.Tests.DomainEvents +using EasyWay.Internals.Clocks; +using Microsoft.Extensions.Time.Testing; + +namespace EasyWay.Tests.DomainEvents { public sealed class DomainEventTests { @@ -11,7 +14,7 @@ public void Test() var expectedDateTime = DateTime.UtcNow.AddMonths(-6); var precision = TimeSpan.FromMilliseconds(50); - Clock.Set(expectedDateTime); + InternalClock.Test(new FakeTimeProvider(expectedDateTime)); // Act var domainEvent = new TestDomainEvent(); diff --git a/tests/EasyWay.Tests/EasyWay.Tests.csproj b/tests/EasyWay.Tests/EasyWay.Tests.csproj index c85ede4..691895b 100644 --- a/tests/EasyWay.Tests/EasyWay.Tests.csproj +++ b/tests/EasyWay.Tests/EasyWay.Tests.csproj @@ -10,6 +10,7 @@ +