diff --git a/src/common/Elsa.Testing.Shared.Integration/WorkflowTestFixture.cs b/src/common/Elsa.Testing.Shared.Integration/WorkflowTestFixture.cs new file mode 100644 index 000000000..8bf03ab4c --- /dev/null +++ b/src/common/Elsa.Testing.Shared.Integration/WorkflowTestFixture.cs @@ -0,0 +1,155 @@ +using Elsa.Extensions; +using Elsa.Features.Services; +using Elsa.Workflows; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Models; +using Elsa.Workflows.State; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Xunit.Abstractions; + +namespace Elsa.Testing.Shared; + +/// +/// A test fixture for integration testing workflows and activities. +/// Provides a fluent API to configure services, run workflows, and capture output. +/// +public class WorkflowTestFixture +{ + private readonly TestApplicationBuilder _testApplicationBuilder; + private IServiceProvider? _services; + + /// + /// Initializes a new instance of the class. + /// + /// The test output helper + public WorkflowTestFixture(ITestOutputHelper testOutputHelper) + { + _testApplicationBuilder = new TestApplicationBuilder(testOutputHelper); + CapturingTextWriter = new CapturingTextWriter(); + _testApplicationBuilder.WithCapturingTextWriter(CapturingTextWriter); + } + + /// + /// Gets the capturing text writer that captures standard output from WriteLine activities. + /// + public CapturingTextWriter CapturingTextWriter { get; } + + /// + /// Gets the service provider. Throws if Build() hasn't been called yet. + /// + private IServiceProvider Services => _services ?? throw new InvalidOperationException("Build() must be called before accessing services"); + + /// + /// Configures Elsa features. + /// + /// Action to configure Elsa + /// The fixture instance for method chaining + [UsedImplicitly] + public WorkflowTestFixture ConfigureElsa(Action configure) + { + _testApplicationBuilder.ConfigureElsa(configure); + return this; + } + + /// + /// Configures the service collection. + /// + /// Action to configure the service collection + /// The fixture instance for method chaining + [UsedImplicitly] + public WorkflowTestFixture ConfigureServices(Action configure) + { + _testApplicationBuilder.ConfigureServices(configure); + return this; + } + + /// + /// Adds a workflow to the service provider. + /// + [UsedImplicitly] + public WorkflowTestFixture AddWorkflow() where T : IWorkflow + { + _testApplicationBuilder.AddWorkflow(); + return this; + } + + /// + /// Adds activities from the assembly containing the specified type. + /// + [UsedImplicitly] + public WorkflowTestFixture AddActivitiesFrom() + { + _testApplicationBuilder.AddActivitiesFrom(); + return this; + } + + /// + /// Adds workflows from the specified relative directory. + /// + /// The path segments of the directory + [UsedImplicitly] + public WorkflowTestFixture WithWorkflowsFromDirectory(params string[] directory) + { + _testApplicationBuilder.WithWorkflowsFromDirectory(directory); + return this; + } + + /// + /// Builds the service provider and populates registries. + /// Must be called before running workflows. + /// + public async Task BuildAsync() + { + _services = _testApplicationBuilder.Build(); + await Services.PopulateRegistriesAsync(); + return this; + } + + /// + /// Runs a workflow and returns the workflow result. + /// Automatically builds the fixture if not already built. + /// + /// The workflow to run + /// Cancellation token + /// The workflow result after execution + public async Task RunWorkflowAsync(IWorkflow workflow, CancellationToken cancellationToken = default) + { + if (_services == null) + await BuildAsync(); + + var workflowRunner = Services.GetRequiredService(); + return await workflowRunner.RunAsync(workflow, cancellationToken: cancellationToken); + } + + /// + /// Runs an activity wrapped in a workflow and returns the workflow result. + /// Automatically builds the fixture if not already built. + /// + /// The activity to run + /// Cancellation token + /// The workflow result after execution + public async Task RunActivityAsync(IActivity activity, CancellationToken cancellationToken = default) + { + if (_services == null) + await BuildAsync(); + + var workflowRunner = Services.GetRequiredService(); + return await workflowRunner.RunAsync(activity, cancellationToken: cancellationToken); + } + + /// + /// Runs a workflow by definition ID and returns the workflow state. + /// Automatically builds the fixture if not already built. + /// + /// The workflow definition ID + /// Optional input dictionary + /// The workflow state after execution + public async Task RunWorkflowAsync(string definitionId, IDictionary? input = null) + { + if (_services == null) + await BuildAsync(); + + return await Services.RunWorkflowUntilEndAsync(definitionId, input); + } +} diff --git a/src/modules/Elsa.Expressions.JavaScript/Activities/RunJavaScript/RunJavaScript.cs b/src/modules/Elsa.Expressions.JavaScript/Activities/RunJavaScript/RunJavaScript.cs index ff5400cfe..60680387b 100644 --- a/src/modules/Elsa.Expressions.JavaScript/Activities/RunJavaScript/RunJavaScript.cs +++ b/src/modules/Elsa.Expressions.JavaScript/Activities/RunJavaScript/RunJavaScript.cs @@ -25,7 +25,7 @@ public class RunJavaScript : CodeActivity /// public RunJavaScript(string script, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line) { - Script = new Input(script); + Script = new(script); } /// diff --git a/test/integration/Elsa.JavaScript.IntegrationTests/JsonConverterTest.cs b/test/integration/Elsa.JavaScript.IntegrationTests/JsonConverterTest.cs index e1d080af5..ac82f2a1b 100644 --- a/test/integration/Elsa.JavaScript.IntegrationTests/JsonConverterTest.cs +++ b/test/integration/Elsa.JavaScript.IntegrationTests/JsonConverterTest.cs @@ -2,14 +2,13 @@ using System.Numerics; using System.Text.Json; using Elsa.Common.Converters; -using Elsa.Expressions.Models; using Elsa.Expressions.JavaScript.Contracts; +using Elsa.Expressions.Models; using Elsa.Testing.Shared; using Microsoft.Extensions.DependencyInjection; -using Xunit; using Xunit.Abstractions; -namespace Elsa.Expressions.JavaScript.IntegrationTests; +namespace Elsa.JavaScript.IntegrationTests; public class JsonConverterTest(ITestOutputHelper testOutputHelper) { private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build(); @@ -23,7 +22,7 @@ public class JsonConverterTest(ITestOutputHelper testOutputHelper) }"; var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister()); var result = (await javaScriptEvaluator.EvaluateAsync(script, typeof(ExpandoObject), expressionExecutionContext))!; - var options = new JsonSerializerOptions() + var options = new JsonSerializerOptions { Converters = { new BigIntegerJsonConverter() } }; @@ -47,9 +46,9 @@ public class JsonConverterTest(ITestOutputHelper testOutputHelper) } [Fact(DisplayName = "BigIntegerJsonConverter Deserialize")] - public async Task Test3() + public Task Test3() { - var options = new JsonSerializerOptions() + var options = new JsonSerializerOptions { Converters = { new BigIntegerJsonConverter() } }; @@ -57,5 +56,6 @@ public class JsonConverterTest(ITestOutputHelper testOutputHelper) BigInteger bigInteger = JsonSerializer.Deserialize("7239948466988781569", options); Assert.Equal(7239948466988781569, bigInteger); + return Task.CompletedTask; } } diff --git a/test/integration/Elsa.JavaScript.IntegrationTests/JsonElementConverterTests.cs b/test/integration/Elsa.JavaScript.IntegrationTests/JsonElementConverterTests.cs index fb490adcb..85aa4b984 100644 --- a/test/integration/Elsa.JavaScript.IntegrationTests/JsonElementConverterTests.cs +++ b/test/integration/Elsa.JavaScript.IntegrationTests/JsonElementConverterTests.cs @@ -1,13 +1,12 @@ using System.Text.Json; -using Elsa.Expressions.Models; using Elsa.Expressions.JavaScript.Contracts; +using Elsa.Expressions.Models; using Elsa.Testing.Shared; using Elsa.Workflows.Memory; using Microsoft.Extensions.DependencyInjection; -using Xunit; using Xunit.Abstractions; -namespace Elsa.Expressions.JavaScript.IntegrationTests; +namespace Elsa.JavaScript.IntegrationTests; public class JsonElementConverterTests(ITestOutputHelper testOutputHelper) { diff --git a/test/integration/Elsa.JavaScript.IntegrationTests/RunJavaScriptTests.cs b/test/integration/Elsa.JavaScript.IntegrationTests/RunJavaScriptTests.cs new file mode 100644 index 000000000..0e3ca8281 --- /dev/null +++ b/test/integration/Elsa.JavaScript.IntegrationTests/RunJavaScriptTests.cs @@ -0,0 +1,101 @@ +using Elsa.Expressions.JavaScript.Activities; +using Elsa.Testing.Shared; +using Elsa.Workflows.Activities; +using Elsa.Workflows.Memory; +using Xunit.Abstractions; + +namespace Elsa.JavaScript.IntegrationTests; + +public class RunJavaScriptTests(ITestOutputHelper testOutputHelper) +{ + private readonly WorkflowTestFixture _fixture = new(testOutputHelper); + + [Theory(DisplayName = "RunJavaScript should execute valid scripts successfully")] + [InlineData("return 1 + 1;")] + [InlineData("return 'Hello World';")] + [InlineData("return 42;")] + public async Task Should_Execute_Valid_Scripts(string script) + { + // Arrange + var activity = new RunJavaScript { Script = new(script), Result = new() }; + + // Act + await _fixture.RunActivityAsync(activity); + + // Assert + Assert.Empty(_fixture.CapturingTextWriter.Lines); + } + + [Theory(DisplayName = "RunJavaScript should set outcomes correctly")] + [InlineData("setOutcome('Success');")] + [InlineData("setOutcomes(['Branch1', 'Branch2', 'Branch3']);")] + public async Task Should_Set_Outcomes(string script) + { + // Arrange + var activity = new RunJavaScript { Script = new(script) }; + + // Act + await _fixture.RunActivityAsync(activity); + + // Assert + Assert.Null(_fixture.CapturingTextWriter.Lines.FirstOrDefault()); + } + + [Theory(DisplayName = "RunJavaScript should not execute invalid scripts")] + [InlineData("")] + [InlineData(" ")] + public async Task Should_Not_Execute_Invalid_Scripts(string script) + { + // Arrange + var activity = new RunJavaScript { Script = new(script), Result = new() }; + + // Act + await _fixture.RunActivityAsync(activity); + + // Assert + Assert.Empty(_fixture.CapturingTextWriter.Lines); + } + + [Fact(DisplayName = "RunJavaScript should access workflow variables")] + public async Task Should_Access_Workflow_Variables() + { + // Arrange + var myVar = new Variable("MyVar", 100); + var script = "return getMyVar();"; + var workflow = new Workflow + { + Root = new RunJavaScript { Script = new(script), Result = new() }, + Variables = { myVar } + }; + + // Act + await _fixture.RunActivityAsync(workflow); + + // Assert - workflow completes successfully + Assert.Empty(_fixture.CapturingTextWriter.Lines); + } + + [Fact(DisplayName = "RunJavaScript should execute complex script with multiple statements and outcomes")] + public async Task Should_Execute_Complex_Script_With_Multiple_Statements() + { + // Arrange + var script = @" + var x = 10; + var y = 20; + var sum = x + y; + if (sum > 25) { + setOutcome('Large'); + } else { + setOutcome('Small'); + } + return sum; + "; + var activity = new RunJavaScript { Script = new(script), Result = new() }; + + // Act + await _fixture.RunActivityAsync(activity); + + // Assert - workflow completes successfully + Assert.Empty(_fixture.CapturingTextWriter.Lines); + } +} diff --git a/test/integration/Elsa.JavaScript.IntegrationTests/ToJsonTests.cs b/test/integration/Elsa.JavaScript.IntegrationTests/ToJsonTests.cs index 384b4ec0d..a75525c1b 100644 --- a/test/integration/Elsa.JavaScript.IntegrationTests/ToJsonTests.cs +++ b/test/integration/Elsa.JavaScript.IntegrationTests/ToJsonTests.cs @@ -1,13 +1,12 @@ using System.Text.Json; -using Elsa.Expressions.Models; using Elsa.Expressions.JavaScript.Contracts; +using Elsa.Expressions.Models; using Elsa.Testing.Shared; using Elsa.Workflows.Memory; using Microsoft.Extensions.DependencyInjection; -using Xunit; using Xunit.Abstractions; -namespace Elsa.Expressions.JavaScript.IntegrationTests; +namespace Elsa.JavaScript.IntegrationTests; public class ToJsonTests(ITestOutputHelper testOutputHelper) { diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Activities/Fork/ForkTests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Activities/Fork/ForkTests.cs index 33239e612..ffabf4de1 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Activities/Fork/ForkTests.cs +++ b/test/integration/Elsa.Workflows.IntegrationTests/Activities/Fork/ForkTests.cs @@ -11,7 +11,7 @@ public class ForkTests private readonly IWorkflowRunner _workflowRunner; private readonly CapturingTextWriter _capturingTextWriter = new(); private readonly IWorkflowBuilderFactory _workflowBuilderFactory; - private IServiceProvider _services; + private readonly IServiceProvider _services; public ForkTests(ITestOutputHelper testOutputHelper) {