elsa-core/test/integration/Elsa.Activities.IntegrationTests/Http/DownloadHttpFileTests.cs
Sipke Schoorstra b0d93d347b
Add unit and integration tests for DownloadHttpFile activity (#7094)
* 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
2025-11-24 14:37:06 +01:00

225 lines
8.6 KiB
C#

using System.Net;
using Elsa.Activities.UnitTests.Http.Helpers;
using Elsa.Extensions;
using Elsa.Http;
using Elsa.Testing.Shared;
using Elsa.Workflows;
using Elsa.Workflows.Models;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Activities.IntegrationTests.Http;
public class DownloadHttpFileTests(ITestOutputHelper testOutputHelper)
{
[Fact(DisplayName = "DownloadHttpFile downloads file successfully")]
public async Task DownloadsFile_Successfully()
{
// Arrange
var fileContent = "Test file content"u8.ToArray();
var handler = CreateFileResponseHandler(fileContent, "document.pdf", "application/pdf");
// Act
var (workflowResult, activity) = await RunActivityAsync("https://example.com/file.pdf", handler);
// Assert
var file = workflowResult.GetActivityOutput<HttpFile>(activity);
Assert.NotNull(file);
Assert.Equal("document.pdf", file.Filename);
Assert.Equal("application/pdf", file.ContentType);
var stream = workflowResult.GetActivityOutput<Stream>(activity, nameof(DownloadHttpFile.ResponseContentStream));
Assert.NotNull(stream);
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
Assert.Equal("Test file content", content);
}
[Fact(DisplayName = "DownloadHttpFile handles POST requests")]
public async Task HandlesPostRequest()
{
// Arrange
var fileContent = "Response content"u8.ToArray();
var handler = CreateFileResponseHandler(fileContent);
// Act
var (workflowResult, activity) = await RunActivityAsync(
"https://api.example.com/download",
handler,
method: "POST",
requestContent: new { id = 123 },
requestContentType: "application/json");
// Assert
var statusCode = workflowResult.GetActivityOutput<int>(activity, nameof(DownloadHttpFile.StatusCode));
Assert.Equal(200, statusCode);
var file = workflowResult.GetActivityOutput<HttpFile>(activity);
Assert.NotNull(file);
}
[Fact(DisplayName = "DownloadHttpFile includes authorization header")]
public async Task IncludesAuthorizationHeader()
{
// Arrange
var requestCapture = new HttpRequestMessage?[1];
var fileContent = "Authorized content"u8.ToArray();
var handler = CreateCapturingHandler(fileContent, requestCapture);
// Act
await RunActivityAsync(
"https://example.com/secure/file.pdf",
handler,
authorization: "Bearer test-token");
// Assert
var capturedRequest = requestCapture[0];
Assert.NotNull(capturedRequest);
Assert.NotNull(capturedRequest.Headers.Authorization);
Assert.Equal("Bearer test-token", capturedRequest.Headers.Authorization.ToString());
}
[Theory(DisplayName = "DownloadHttpFile extracts filename correctly")]
[InlineData("https://example.com/generate-report", "annual-report.xlsx", "annual-report.xlsx")] // From Content-Disposition
[InlineData("https://example.com/downloads/image.png", null, "image.png")] // From URL
public async Task ExtractsFilename(string url, string? contentDispositionFilename, string expectedFilename)
{
// Arrange
var fileContent = "File data"u8.ToArray();
var handler = CreateFileResponseHandler(fileContent, contentDispositionFilename);
// Act
var (workflowResult, activity) = await RunActivityAsync(url, handler);
// Assert
var file = workflowResult.GetActivityOutput<HttpFile>(activity);
Assert.NotNull(file);
Assert.Equal(expectedFilename, file.Filename);
}
[Theory(DisplayName = "DownloadHttpFile handles various status codes")]
[InlineData(200)]
[InlineData(201)]
[InlineData(404)]
public async Task HandlesStatusCodes(int statusCode)
{
// Arrange
var handler = CreateFileResponseHandler(statusCode: (HttpStatusCode)statusCode);
// Act
var (workflowResult, activity) = await RunActivityAsync(
"https://example.com/file.pdf",
handler,
expectedStatusCodes: [statusCode]);
// Assert
var actualStatusCode = workflowResult.GetActivityOutput<int>(activity, nameof(DownloadHttpFile.StatusCode));
Assert.Equal(statusCode, actualStatusCode);
}
[Fact(DisplayName = "DownloadHttpFile sets response headers")]
public async Task SetsResponseHeaders()
{
// Arrange
var additionalHeaders = new Dictionary<string, string>
{
{ "X-Rate-Limit", "100" },
{ "X-Request-Id", "abc123" }
};
var handler = CreateFileResponseHandler(additionalHeaders: additionalHeaders);
// Act
var (workflowResult, activity) = await RunActivityAsync("https://api.example.com/download", handler);
// Assert
var responseHeaders = workflowResult.GetActivityOutput<HttpHeaders>(activity, nameof(DownloadHttpFile.ResponseHeaders));
Assert.NotNull(responseHeaders);
Assert.Contains(responseHeaders.Keys, k => k.Equals("X-Rate-Limit", StringComparison.OrdinalIgnoreCase));
Assert.Contains(responseHeaders.Keys, k => k.Equals("X-Request-Id", StringComparison.OrdinalIgnoreCase));
}
private async Task<(RunWorkflowResult Result, DownloadHttpFile Activity)> RunActivityAsync(
string url,
HttpMessageHandler handler,
string method = "GET",
object? requestContent = null,
string? requestContentType = null,
string? authorization = null,
int[]? expectedStatusCodes = null)
{
var fixture = CreateFixture(handler);
var activity = new DownloadHttpFile
{
Url = new(new Uri(url)),
Method = new(method),
ExpectedStatusCodes = new(expectedStatusCodes ?? [200])
};
if (requestContent != null)
activity.RequestContent = new(requestContent);
if (requestContentType != null)
activity.RequestContentType = new(requestContentType);
if (authorization != null)
activity.Authorization = new(authorization);
var result = await fixture.RunActivityAsync(activity);
return (result, activity);
}
private WorkflowTestFixture CreateFixture(HttpMessageHandler handler) =>
new WorkflowTestFixture(testOutputHelper)
.ConfigureElsa(elsa => elsa.UseHttp(http =>
{
http.HttpClientBuilder = builder => builder.ConfigurePrimaryHttpMessageHandler(() => handler);
}));
private static HttpMessageHandler CreateFileResponseHandler(
byte[]? content = null,
string? filename = null,
string? contentType = null,
HttpStatusCode statusCode = HttpStatusCode.OK,
Dictionary<string, string>? additionalHeaders = null) =>
new TestHttpMessageHandler((_, _) =>
{
// Response ownership is transferred to the caller (HttpClient), which will dispose it
var response = new HttpResponseMessage(statusCode);
// Always set Content (even if empty) because DownloadHttpFile accesses Content.Headers
var actualContent = content ?? (statusCode == HttpStatusCode.OK ? "Default file content"u8.ToArray() : []);
var actualContentType = contentType ?? "application/octet-stream";
response.Content = new ByteArrayContent(actualContent);
response.Content.Headers.ContentType = new(actualContentType);
if (filename != null)
{
response.Content.Headers.ContentDisposition = new("attachment")
{
FileName = filename
};
}
if (additionalHeaders != null)
foreach (var header in additionalHeaders)
response.Headers.Add(header.Key, header.Value);
return Task.FromResult(response);
});
private static HttpMessageHandler CreateCapturingHandler(byte[] content, HttpRequestMessage?[] capture) =>
new TestHttpMessageHandler((request, _) =>
{
capture[0] = request;
// Response ownership is transferred to the caller (HttpClient), which will dispose it
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(content)
};
response.Content.Headers.ContentType = new("application/octet-stream");
return Task.FromResult(response);
});
}