elsa-core/test/unit/Elsa.Activities.UnitTests/Http/Helpers/ActivityTestFixtureHttpExtensions.cs
Sipke Schoorstra 84c04341f9
Add unit tests for FlowSendHttpRequest activity and refactor helper utilities (#6997)
* Add unit tests for `FlowFork` activity and enhance test utilities

- Introduce `FlowForkTests` to validate activity behavior with various branch configurations.
- Extend `ActivityExecutionContextExtensions` with methods to retrieve and check outcomes.
- Modify `ActivityTestFixture` to ensure activities transition to `Running` status before execution.

* Add unit tests for `FlowSwitch` activity and extend test utilities

- Implement `FlowSwitchTests` to verify behavior based on switch cases, modes, and literal expressions.
- Enhance `ActivityExecutionContextExtensions` with methods to retrieve and check outcomes in execution context.

* Update test guidelines to include examples for checking activity outcomes

- Add unit test examples for validating multiple and default outcomes.
- Extend documentation to describe new `ActivityExecutionContextExtensions` methods: `GetOutcomes` and `HasOutcome`.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Simplify `GetOutcomes` method in `ActivityExecutionContextExtensions`.

* Add unit tests for `FlowSendHttpRequest` activity and refactor helper utilities

- Introduce `FlowSendHttpRequestTests` to validate activity behavior across various scenarios.
- Add utility methods in `SendHttpRequestTestHelpers` for creating mock responses and exceptions.
- Refactor `Helpers` namespace to `Http.Helpers` for better organization.
- Update project file to reflect namespace adjustments.

* Update test/unit/Elsa.Activities.UnitTests/Elsa.Activities.UnitTests.csproj

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-21 15:36:54 +02:00

142 lines
5.6 KiB
C#

using System.Net;
using System.Text;
using Elsa.Http;
using Elsa.Http.ContentWriters;
using Elsa.Http.Parsers;
using Elsa.Resilience;
using Elsa.Testing.Shared;
using Elsa.Workflows;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
namespace Elsa.Activities.UnitTests.Http.Helpers;
/// <summary>
/// Extension methods for configuring HTTP-related services in ActivityTestFixture.
/// </summary>
public static class ActivityTestFixtureHttpExtensions
{
/// <summary>
/// Configures services commonly needed for HTTP activity testing.
/// This is a convenience method that combines HTTP services, mock HTTP client factory, and mock resilient invoker.
/// </summary>
/// <param name="fixture">The test fixture to configure</param>
/// <param name="responseHandler">Handler for HTTP requests, or null to use a default OK response</param>
/// <returns>The fixture instance for method chaining</returns>
public static ActivityTestFixture WithHttpServices(
this ActivityTestFixture fixture,
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>>? responseHandler = null)
{
fixture.ConfigureServices(services =>
{
var defaultHandler = responseHandler ?? ((_, _) => Task.FromResult(CreateHttpResponse(HttpStatusCode.OK)));
var mockHttpClientFactory = CreateMockHttpClientFactory(defaultHandler);
services.AddSingleton(mockHttpClientFactory);
services.AddSingleton(CreateMockResilientActivityInvoker());
AddHttpServices(services);
services.AddLogging();
});
return fixture;
}
/// <summary>
/// Creates a simple HTTP response message with specified status code and optional JSON content.
/// </summary>
/// <param name="statusCode">The HTTP status code for the response</param>
/// <param name="jsonContent">Optional JSON content for the response body</param>
/// <param name="additionalHeaders">Optional additional headers to add to the response</param>
/// <returns>An HttpResponseMessage configured with the specified parameters</returns>
public static HttpResponseMessage CreateHttpResponse(
HttpStatusCode statusCode,
string? jsonContent = null,
Dictionary<string, string>? additionalHeaders = null)
{
var response = new HttpResponseMessage(statusCode);
if (jsonContent != null)
{
response.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
}
if (additionalHeaders != null)
{
foreach (var header in additionalHeaders)
{
response.Headers.Add(header.Key, header.Value);
}
}
return response;
}
/// <summary>
/// Adds all HTTP-related services to the service collection.
/// This includes content factories, parsers, and HTTP client services.
/// </summary>
private static void AddHttpServices(IServiceCollection services)
{
// Add all required HTTP services
services.AddSingleton<IHttpContentFactory, JsonContentFactory>();
services.AddSingleton<IHttpContentFactory, TextContentFactory>();
services.AddSingleton<IHttpContentFactory, XmlContentFactory>();
services.AddSingleton<IHttpContentFactory, FormUrlEncodedHttpContentFactory>();
// Add HTTP content parsers
AddHttpContentParsers(services);
// Add other required services
services.AddHttpClient();
}
/// <summary>
/// Adds HTTP content parsers to the service collection.
/// These parsers are responsible for parsing different content types in HTTP responses.
/// </summary>
private static void AddHttpContentParsers(IServiceCollection services)
{
services.AddSingleton<IHttpContentParser, JsonHttpContentParser>();
services.AddSingleton<IHttpContentParser, PlainTextHttpContentParser>();
services.AddSingleton<IHttpContentParser, XmlHttpContentParser>();
services.AddSingleton<IHttpContentParser, TextHtmlHttpContentParser>();
services.AddSingleton<IHttpContentParser, FileHttpContentParser>();
}
/// <summary>
/// Creates a mock IResilientActivityInvoker that directly executes the provided action.
/// Useful for testing activities that depend on resilient execution without the complexity of retry policies.
/// </summary>
private static IResilientActivityInvoker CreateMockResilientActivityInvoker()
{
var mock = Substitute.For<IResilientActivityInvoker>();
// Configure the mock to simply execute the provided action directly
mock.InvokeAsync(
Arg.Any<IResilientActivity>(),
Arg.Any<ActivityExecutionContext>(),
Arg.Any<Func<Task<HttpResponseMessage>>>(),
Arg.Any<CancellationToken>())
.Returns(callInfo =>
{
var action = callInfo.ArgAt<Func<Task<HttpResponseMessage>>>(2);
return action.Invoke();
});
return mock;
}
/// <summary>
/// Creates a mock HTTP client factory with a test handler for controlled HTTP responses.
/// </summary>
private static IHttpClientFactory CreateMockHttpClientFactory(
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> responseHandler)
{
var mockHttpClientFactory = Substitute.For<IHttpClientFactory>();
var testHandler = new TestHttpMessageHandler(responseHandler);
var httpClient = new HttpClient(testHandler);
mockHttpClientFactory.CreateClient(Arg.Any<string>()).Returns(httpClient);
return mockHttpClientFactory;
}
}