From f020c5e9d3623343bd1b4290e57d252a6fccf101 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 24 Nov 2025 14:43:42 +0100 Subject: [PATCH] Adds integration tests for core activities (#7100) * Add unit and integration tests for `DownloadHttpFile` activity - Developed comprehensive unit tests for the `DownloadHttpFile` activity validating method execution (GET, POST), URL handling, headers, and status codes. - Added integration tests to verify functionality like authorization headers, filename extraction, and response stream handling. - Updated `IntegrationTests` project references to include the required `Elsa.Http` module. * Refactor `DownloadHttpFileTests` to simplify test setup and improve reusability - Consolidated test initialization logic into `RunActivityAsync` for cleaner and reusable test setup. - Updated existing tests to use `RunActivityAsync`, reducing boilerplate code. - Unified filename extraction tests into a parameterized theory to improve test coverage and clarity. - Simplified helper handlers with expression-bodied members for readability. * Add comments to clarify HttpResponseMessage disposal responsibility in `DownloadHttpFileTests`. * Ensure `HttpResponseMessage.Content` is always set to prevent null reference issues in `DownloadHttpFileTests`. * Refactor `DownloadHttpFileTests` to ensure `HttpResponseMessage.Content` is always initialized and simplify content assignment logic. * Update `DownloadHttpFileTests` to set an empty `HttpResponseMessage.Content` for testing `HasContent` behavior * Add `GitHubActionsTestLogger` package to test projects and refactor `Directory.Packages.props` - Updated unit and integration test projects to include `GitHubActionsTestLogger` for improved test reporting in CI. - Refactored `Directory.Packages.props` to add conditional dependencies for .NET 8, 9, and 10 compatibility. * Remove `GitHubActionsTestLogger` package from test projects. * Add project reference for unit tests and update `DownloadHttpFileTests` - Included `Elsa.Activities.UnitTests` project reference in `IntegrationTests` to reuse helpers. - Removed redundant `TestHttpMessageHandler` by utilizing shared helper from `UnitTests`. * Refactor `DownloadHttpFileTests` to remove unused methods and simplify imports * Update target framework to .NET 10 and upgrade `Nuke.Components` package to v10.0.0 * Add integration tests for smoke testing all core workflow activities - Introduced `ActivitiesSmokeTests` to validate basic functionality of core workflow activities, including control flow and data manipulation. - Added comprehensive `ActivitiesSmokeTestWorkflow` to test activities such as `Start`, `Finish`, `If`, `Switch`, `For`, `While`, `ForEach`, `SetVariable`, and `SetOutput`. * Refactor `ActivitiesSmokeTestWorkflow` to reorder and clarify activity test cases * Update test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTestWorkflow.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../SmokeTests/ActivitiesSmokeTestWorkflow.cs | 209 ++++++++++++++++++ .../SmokeTests/ActivitiesSmokeTests.cs | 109 +++++++++ 2 files changed, 318 insertions(+) create mode 100644 test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTestWorkflow.cs create mode 100644 test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTests.cs diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTestWorkflow.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTestWorkflow.cs new file mode 100644 index 000000000..1bda08cf9 --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTestWorkflow.cs @@ -0,0 +1,209 @@ +using Elsa.Workflows.Activities; +using Elsa.Workflows.Management.Activities.SetOutput; +using Elsa.Workflows.Memory; +using Elsa.Workflows.Models; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.SmokeTests; + +/// +/// Comprehensive smoke test workflow that exercises basic control flow and data manipulation activities. +/// Tests: Start, Sequence, Break, Complete, If, Switch, While, For, ForEach, +/// WriteLine, SetName, SetVariable<T>, SetVariable (untyped), SetOutput +/// +public class ActivitiesSmokeTestWorkflow : WorkflowBase +{ + protected override void Build(IWorkflowBuilder workflow) + { + // Variables for testing + var counter = new Variable("Counter", 0); + var name = new Variable("Name", "Initial"); + var result = new Variable("Result", ""); + var items = new Variable>("Items", ["A", "B", "C"]); + var currentItem = new Variable("CurrentItem", ""); + var loopCounter = new Variable("LoopCounter", 0); + var untypedVar = new Variable("UntypedVar", null); + var switchValue = new Variable("SwitchValue", 2); + + workflow.WithVariables(counter, name, result, items, currentItem, loopCounter, untypedVar, switchValue); + + workflow.Root = new Sequence + { + Activities = + { + // Test Start activity + new Start(), + + // Test SetName + new SetName + { + Value = new("SmokeTestWorkflow") + }, + + // Test WriteLine + new WriteLine(context => "=== Smoke Test Started ==="), + + // Test SetVariable + new SetVariable + { + Variable = name, + Value = new("Updated Name") + }, + new WriteLine(context => $"Name: {name.Get(context)}"), + + // Test SetVariable (untyped) + new SetVariable + { + Variable = untypedVar, + Value = new("Untyped value") + }, + new WriteLine(context => $"Untyped: {untypedVar.Get(context)}"), + + // Test If activity (condition true) + new If(() => true) + { + Then = new Sequence + { + Activities = + { + new WriteLine("If branch: True path executed"), + new SetVariable { Variable = counter, Value = new(10) } + } + }, + Else = new WriteLine("If branch: False path (should not execute)") + }, + + // Test Switch activity + new Switch + { + Cases = + { + new( + "Case1", + context => ValueTask.FromResult(switchValue.Get(context) == 1), + new WriteLine("Switch: Case 1 (should not execute)") + ), + new( + "Case2", + context => ValueTask.FromResult(switchValue.Get(context) == 2), + new Sequence + { + Activities = + { + new WriteLine("Switch: Case 2 executed"), + new SetVariable { Variable = result, Value = new("Switch-2") } + } + } + ), + new( + "Case3", + context => ValueTask.FromResult(switchValue.Get(context) == 3), + new WriteLine("Switch: Case 3 (should not execute)") + ) + }, + Default = new WriteLine("Switch: Default (should not execute)") + }, + + // Test For loop with Break + new Sequence + { + Activities = + { + new WriteLine("For loop: Starting"), + new For + { + Start = new(0), + End = new(100), + Step = new(1), + Body = new Sequence + { + Activities = + { + new SetVariable { Variable = loopCounter, Value = new(context => loopCounter.Get(context) + 1) }, + // Break after 3 iterations + new If(context => loopCounter.Get(context) >= 3) + { + Then = new Break() + } + } + } + }, + new WriteLine(context => $"For loop: Completed with {loopCounter.Get(context)} iterations") + } + }, + + // Test While loop with Break + new Sequence + { + Activities = + { + new SetVariable { Variable = loopCounter, Value = new(0) }, + new WriteLine("While loop: Starting"), + new While(() => true) + { + Body = new Sequence + { + Activities = + { + new SetVariable { Variable = loopCounter, Value = new(context => loopCounter.Get(context) + 1) }, + new WriteLine(context => $"While loop: Iteration {loopCounter.Get(context)}"), + // Break after 3 iterations + new If(context => loopCounter.Get(context) >= 3) + { + Then = new Break() + } + } + } + }, + new WriteLine(context => $"While loop: Completed with {loopCounter.Get(context)} iterations") + } + }, + + // Test ForEach with Break + new Sequence + { + Activities = + { + new SetVariable { Variable = loopCounter, Value = new(0) }, + new WriteLine("ForEach loop: Starting"), + new ForEach + { + Items = new(items), + CurrentValue = new(currentItem), + Body = new Sequence + { + Activities = + { + new SetVariable { Variable = loopCounter, Value = new(context => loopCounter.Get(context) + 1) }, + new WriteLine(context => $"ForEach: Item '{currentItem.Get(context)}'"), + // Break after processing 2 items (A, B) + new If(context => loopCounter.Get(context) >= 2) + { + Then = new Break() + } + } + } + }, + new WriteLine(context => $"ForEach loop: Completed with {loopCounter.Get(context)} items processed") + } + }, + + // Test SetOutput + new SetOutput + { + OutputName = new("FinalResult"), + OutputValue = new(context => $"Counter={counter.Get(context)}, Name={name.Get(context)}, Result={result.Get(context)}") + }, + + // Test Finish and End activities + new Finish(), + new End(), + + // Test Complete activity (ends workflow immediately) + new Complete(), + + // This should not execute due to Complete + new WriteLine("After Complete (should not execute)") + } + }; + } +} diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTests.cs new file mode 100644 index 000000000..16d27936a --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/SmokeTests/ActivitiesSmokeTests.cs @@ -0,0 +1,109 @@ +using Elsa.Testing.Shared; +using Elsa.Workflows.Models; +using Xunit.Abstractions; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.SmokeTests; + +/// +/// Smoke tests that verify basic functionality of core workflow activities. +/// +public class ActivitiesSmokeTests(ITestOutputHelper testOutputHelper) +{ + private readonly WorkflowTestFixture _fixture = new(testOutputHelper); + + [Fact(DisplayName = "Smoke test executes all core activities successfully")] + public async Task SmokeTest_ExecutesAllActivities_Successfully() + { + // Act + var (result, _) = await RunWorkflowAndCaptureOutput(); + + // Assert - Workflow completed successfully + Assert.Equal(WorkflowStatus.Finished, result.WorkflowState.Status); + Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus); + + // Verify outputs were set correctly + var outputs = result.WorkflowState.Output; + Assert.NotNull(outputs); + Assert.True(outputs.TryGetValue("FinalResult", out var finalResultObj)); + + var finalResult = finalResultObj?.ToString(); + Assert.NotNull(finalResult); + Assert.Contains("Counter=10", finalResult); + Assert.Contains("Name=Updated Name", finalResult); + Assert.Contains("Result=Switch-2", finalResult); + } + + [Fact(DisplayName = "Smoke test verifies all activities were executed")] + public async Task SmokeTest_VerifiesActivityExecution() + { + // Act + var (_, lines) = await RunWorkflowAndCaptureOutput(); + + // Assert - Verify key activities executed by checking WriteLine outputs + Assert.Contains(lines, line => line.Contains("=== Smoke Test Started ===")); + Assert.Contains(lines, line => line.Contains("Name: Updated Name")); + Assert.Contains(lines, line => line.Contains("Untyped: Untyped value")); + Assert.Contains(lines, line => line.Contains("If branch: True path executed")); + Assert.Contains(lines, line => line.Contains("Switch: Case 2 executed")); + Assert.Contains(lines, line => line.Contains("For loop: Completed with 3 iterations")); + Assert.Contains(lines, line => line.Contains("While loop: Completed with 3 iterations")); + Assert.Contains(lines, line => line.Contains("ForEach loop: Completed with 2 items processed")); + + // Verify activities after Complete did NOT execute + Assert.DoesNotContain(lines, line => line.Contains("After Complete (should not execute)")); + } + + [Fact(DisplayName = "Break activity works correctly in different loop contexts")] + public async Task SmokeTest_BreakActivity_WorksInDifferentLoops() + { + // Act + var (_, lines) = await RunWorkflowAndCaptureOutput(); + + // Assert - Verify Break worked in For loop (stopped at 3 iterations, not 100) + Assert.Contains(lines, line => line.Contains("For loop: Completed with 3 iterations")); + + // Verify Break worked in While loop (stopped at 3 iterations, didn't run infinitely) + Assert.Contains(lines, line => line.Contains("While loop: Iteration 1")); + Assert.Contains(lines, line => line.Contains("While loop: Iteration 2")); + Assert.Contains(lines, line => line.Contains("While loop: Iteration 3")); + Assert.Contains(lines, line => line.Contains("While loop: Completed with 3 iterations")); + + // Verify Break worked in ForEach (stopped after 2 items: A, B, not C) + Assert.Contains(lines, line => line.Contains("ForEach: Item 'A'")); + Assert.Contains(lines, line => line.Contains("ForEach: Item 'B'")); + Assert.DoesNotContain(lines, line => line.Contains("ForEach: Item 'C'")); + Assert.Contains(lines, line => line.Contains("ForEach loop: Completed with 2 items processed")); + } + + [Fact(DisplayName = "Switch activity executes correct case")] + public async Task SmokeTest_SwitchActivity_ExecutesCorrectCase() + { + // Act + var (_, lines) = await RunWorkflowAndCaptureOutput(); + + // Assert - Only Case 2 should execute + Assert.DoesNotContain(lines, line => line.Contains("Switch: Case 1 (should not execute)")); + Assert.Contains(lines, line => line.Contains("Switch: Case 2 executed")); + Assert.DoesNotContain(lines, line => line.Contains("Switch: Case 3 (should not execute)")); + Assert.DoesNotContain(lines, line => line.Contains("Switch: Default (should not execute)")); + } + + [Fact(DisplayName = "If activity executes correct branch")] + public async Task SmokeTest_IfActivity_ExecutesCorrectBranch() + { + // Act + var (_, lines) = await RunWorkflowAndCaptureOutput(); + + // Assert - Only Then branch should execute + Assert.Contains(lines, line => line.Contains("If branch: True path executed")); + Assert.DoesNotContain(lines, line => line.Contains("If branch: False path (should not execute)")); + } + + private async Task<(RunWorkflowResult Result, List Lines)> RunWorkflowAndCaptureOutput() + { + var workflow = new ActivitiesSmokeTestWorkflow(); + var result = await _fixture.RunWorkflowAsync(workflow); + var lines = _fixture.CapturingTextWriter.Lines.ToList(); + return (result, lines); + } +}