From c6ab42df648342a6001bbab5d697c035bd72a324 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 22 Oct 2025 23:00:23 +0200 Subject: [PATCH] Add unit tests for `StartAt` activity, enhance `ActivityTestFixture`, and update dependencies - Introduced `StartAtTests` to validate behavior for immediate, future, and past executions of the `StartAt` activity. - Updated `ActivityTestFixture` to include `IStimulusHasher` for test setup. - Modified project references to include `Elsa.Scheduling` in unit tests. --- .../ActivityTestFixture.cs | 1 + .../Elsa.Activities.UnitTests.csproj | 1 + .../Scheduling/StartAtTests.cs | 122 ++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 test/unit/Elsa.Activities.UnitTests/Scheduling/StartAtTests.cs diff --git a/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs b/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs index 630698f29..7a163e36e 100644 --- a/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs +++ b/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs @@ -166,6 +166,7 @@ public class ActivityTestFixture services.AddSingleton(); services.AddSingleton(_ => Substitute.For()); services.AddSingleton(_ => Substitute.For()); + services.AddSingleton(); services.AddSingleton(_ => Substitute.For()); services.AddSingleton(); services.AddSingleton(); diff --git a/test/unit/Elsa.Activities.UnitTests/Elsa.Activities.UnitTests.csproj b/test/unit/Elsa.Activities.UnitTests/Elsa.Activities.UnitTests.csproj index 5b8d3f4e6..09b49123c 100644 --- a/test/unit/Elsa.Activities.UnitTests/Elsa.Activities.UnitTests.csproj +++ b/test/unit/Elsa.Activities.UnitTests/Elsa.Activities.UnitTests.csproj @@ -2,6 +2,7 @@ + diff --git a/test/unit/Elsa.Activities.UnitTests/Scheduling/StartAtTests.cs b/test/unit/Elsa.Activities.UnitTests/Scheduling/StartAtTests.cs new file mode 100644 index 000000000..e5b468796 --- /dev/null +++ b/test/unit/Elsa.Activities.UnitTests/Scheduling/StartAtTests.cs @@ -0,0 +1,122 @@ +using Elsa.Common; +using Elsa.Scheduling.Activities; +using Elsa.Scheduling.Bookmarks; +using Elsa.Testing.Shared; +using Elsa.Workflows; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; + +namespace Elsa.Activities.UnitTests.Scheduling; + +public class StartAtTests +{ + [Fact] + public async Task Should_Complete_Immediately_When_IsTrigger() + { + // Arrange + var futureTime = DateTimeOffset.UtcNow.AddHours(1); + var startAt = new StartAt(futureTime); + + // Act + var context = await new ActivityTestFixture(startAt) + .ConfigureContext(ctx => ctx.WorkflowExecutionContext.TriggerActivityId = ctx.Activity.Id) + .ExecuteAsync(); + + // Assert + Assert.Equal(ActivityStatus.Completed, context.Status); + Assert.Empty(context.WorkflowExecutionContext.Bookmarks); + } + + [Theory] + [InlineData(-1, false)] // Past + [InlineData(0, false)] // Now + [InlineData(1, true)] // Future + public async Task Should_Complete_Or_CreateBookmark_BasedOn_DateTime(int hoursOffset, bool shouldCreateBookmark) + { + // Arrange + var now = DateTimeOffset.UtcNow; + var executeAt = now.AddHours(hoursOffset); + var startAt = new StartAt(executeAt); + + // Act + var context = await ExecuteAsync(startAt, now); + + // Assert + if (shouldCreateBookmark) + { + Assert.Equal(ActivityStatus.Running, context.Status); + var bookmark = Assert.Single(context.WorkflowExecutionContext.Bookmarks); + var payload = Assert.IsType(bookmark.Payload); + Assert.Equal(executeAt, payload.ExecuteAt); + } + else + { + Assert.Equal(ActivityStatus.Completed, context.Status); + Assert.Empty(context.WorkflowExecutionContext.Bookmarks); + } + } + + [Theory] + [InlineData(1)] + [InlineData(24)] + [InlineData(168)] + public async Task Should_CreateBookmark_WithCorrectExecuteAt(int hoursInFuture) + { + // Arrange + var futureTime = DateTimeOffset.UtcNow.AddHours(hoursInFuture); + var startAt = new StartAt(futureTime); + + // Act + var context = await ExecuteAsync(startAt); + + // Assert + var bookmark = Assert.Single(context.WorkflowExecutionContext.Bookmarks); + var payload = Assert.IsType(bookmark.Payload); + Assert.Equal(futureTime, payload.ExecuteAt); + } + + [Fact] + public async Task Should_RecordExecutedAt_InJournalData() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var futureTime = now.AddHours(1); + var startAt = new StartAt(futureTime); + + // Act + var context = await ExecuteAsync(startAt, now); + + // Assert + Assert.True(context.JournalData.ContainsKey("Executed At")); + Assert.Equal(now, context.JournalData["Executed At"]); + } + + [Theory] + [InlineData(-1)] + [InlineData(-24)] + [InlineData(-168)] + public async Task Should_Complete_When_DateTime_InPast_ByVariousAmounts(int hoursPast) + { + // Arrange + var now = DateTimeOffset.UtcNow; + var pastTime = now.AddHours(hoursPast); + var startAt = new StartAt(pastTime); + + // Act + var context = await ExecuteAsync(startAt, now); + + // Assert + Assert.Equal(ActivityStatus.Completed, context.Status); + Assert.Empty(context.WorkflowExecutionContext.Bookmarks); + } + + private static async Task ExecuteAsync(StartAt activity, DateTimeOffset? clockTime = null) + { + var clock = Substitute.For(); + clock.UtcNow.Returns(clockTime ?? DateTimeOffset.UtcNow); + + return await new ActivityTestFixture(activity) + .ConfigureServices(services => services.AddSingleton(clock)) + .ExecuteAsync(); + } +} \ No newline at end of file