Addresses warnings and enforces null safety (#7050)

* Simplify test setup by removing `Input` wrapper and fix trimming warnings with `UnconditionalSuppressMessage` annotations.

* Add trimming warning suppression to `ArgumentJsonConverter` with `UnconditionalSuppressMessage`

- Annotated `Read` method to suppress `IL2055` warning caused by dynamic type resolution.
- Provided justification reflecting dynamic type registration in the well-known type registry.

* Add root namespace to project file and suppress EF Core usage warning

- Added `RootNamespace` property to `Elsa.Persistence.EFCore.Common.csproj` for consistency.
- Annotated `DbSchemaAwareMigrationAssembly` with `SuppressMessage` to suppress EF1001 usage warning and provide justification.

* Suppress obsolete API warnings in workflow runtime projects

- Added `#pragma warning disable CS0618` to suppress obsolete member warnings in `ObsoleteWorkflowRuntime.cs`, `LocalWorkflowRuntime.Obsolete.cs`, and `DistributedWorkflowRuntime.Obsolete.cs`.
- Updated `Directory.Build.props` to globally suppress `CS0618` for backward compatibility during migration.

* Annotate properties with non-nullable default initializers across modules to enforce null safety.

* Add `Priority` and `Deprecated` properties to `StorageDriverDescriptor` model

* Update src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.Obsolete.cs

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

* Update src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs

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

* Update src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.Obsolete.cs

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

* Clean up XML comments and improve consistency across modules.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Sipke Schoorstra 2025-11-12 15:53:23 +01:00 committed by GitHub
parent 8747151330
commit 490c8a2c9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 98 additions and 81 deletions

View file

@ -33,7 +33,9 @@
</PropertyGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);CS0162;CS1591</NoWarn>
<!-- Obsolete API warnings - Suppressed for backward compatibility during migration period -->
<NoWarn>$(NoWarn);CS0618</NoWarn>
<!-- IL trimming warnings -->
<NoWarn>$(NoWarn);IL2026;IL2046;IL2057;IL2067;IL2070;IL2072;IL2075;IL2087;IL2091</NoWarn>
<NoWarn>$(NoWarn);IL2026;IL2046;IL2057;IL2067;IL2070;IL2072;IL2075;IL2087;IL2091;IL2111</NoWarn>
</PropertyGroup>
</Project>

View file

@ -9,5 +9,5 @@ public class RetryAttemptRecord : Entity
public string WorkflowInstanceId { get; set; } = null!;
public int AttemptNumber { get; set; }
public TimeSpan RetryDelay { get; set; }
public IDictionary<string, string?> Details { get; set; }
public IDictionary<string, string?> Details { get; set; } = null!;
}

View file

@ -5,4 +5,6 @@ namespace Elsa.Api.Client.Resources.StorageDrivers.Models;
/// </summary>
/// <param name="TypeName">The type name of the storage driver.</param>
/// <param name="DisplayName">The display name of the storage driver.</param>
/// <param name="Priority">The priority of the storage driver.</param>
/// <param name="Deprecated">Indicates whether the storage driver is deprecated.</param>
public record StorageDriverDescriptor(string TypeName, string DisplayName, double Priority = 0, bool Deprecated = false);

View file

@ -42,8 +42,7 @@ public static class HandlerExtensions
/// </summary>
/// <param name="handler">The handler to invoke.</param>
/// <param name="handleMethod">The handle method.</param>
/// <param name="notificationContext">The notification to handle.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="notificationContext">The notification context containing the notification and cancellation token.</param>
public static Task InvokeAsync(this INotificationHandler handler, MethodBase handleMethod, NotificationContext notificationContext)
{
var notification = notificationContext.Notification;

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Elsa.Mediator.Contexts;
using Elsa.Mediator.Contracts;
using Elsa.Mediator.Middleware.Command.Contracts;
@ -13,6 +14,7 @@ namespace Elsa.Mediator.Middleware.Command.Components;
public class CommandHandlerInvokerMiddleware(CommandMiddlewareDelegate next) : ICommandMiddleware
{
/// <inheritdoc />
[UnconditionalSuppressMessage("Trimming", "IL2060:Call to MakeGenericMethod can not be statically analyzed", Justification = "The result type is determined at runtime from command types and handlers are registered in DI.")]
public async ValueTask InvokeAsync(CommandContext context)
{
// Find all handlers for the specified command.

View file

@ -28,6 +28,7 @@ public class DefaultMediator : IMediator
/// <param name="commandPipeline">The command pipeline.</param>
/// <param name="notificationPipeline">The notification pipeline.</param>
/// <param name="options">The mediator options.</param>
/// <param name="serviceProvider">The service provider.</param>
public DefaultMediator(
IRequestPipeline requestPipeline,
ICommandPipeline commandPipeline,

View file

@ -2,6 +2,6 @@ namespace Elsa.Alterations.Core.Models;
public class NewAlterationJob
{
public string PlanId { get; set; }
public string WorkflowInstanceId { get; set; }
public string PlanId { get; set; } = null!;
public string WorkflowInstanceId { get; set; } = null!;
}

View file

@ -1,6 +1,6 @@
namespace Elsa.Alterations.Endpoints.Alterations.Submit;
/// <summary>
/// The response from the <see cref="Execute"/> endpoint.
/// The response from the Submit endpoint.
/// </summary>
public record Response(string PlanId);

View file

@ -3,7 +3,7 @@ using JetBrains.Annotations;
namespace Elsa.Common.DistributedHosting;
/// <summary>
/// Provides options related to distributed locking, which is used by <see cref="DefaultWorkflowRuntime"/>.
/// Provides options related to distributed locking, which is used by the workflow runtime.
/// </summary>
[UsedImplicitly]
public class DistributedLockingOptions

View file

@ -9,8 +9,9 @@ namespace Elsa.Expressions.JavaScript.Extensions;
public static class TypeAliasRegistryExtensions
{
/// <summary>
/// Register type <see cref="T"/> with the specified alias.
/// Register type <typeparamref name="T"/> with the specified alias.
/// </summary>
/// <typeparam name="T">The type to register.</typeparam>
public static void RegisterType<T>(this ITypeAliasRegistry registry, string alias) => registry.RegisterType(typeof(T), alias);
/// <summary>

View file

@ -7,10 +7,10 @@ namespace Elsa.Http.Contexts;
/// </summary>
/// <param name="Manager">The manager.</param>
/// <param name="Content">The content to get downloadables from.</param>
/// <param name="ETag">An optional ETag.</param>
/// <param name="Options">The downloadable options.</param>
/// <param name="CancellationToken">The cancellation token.</param>
public record DownloadableContext(
IDownloadableManager Manager,
object Content,
IDownloadableManager Manager,
object Content,
DownloadableOptions Options,
CancellationToken CancellationToken);

View file

@ -5,7 +5,7 @@ using Elsa.Workflows.UIHints.Dropdown;
namespace Elsa.Http.UIHints;
/// <summary>
/// Provides options for the <see cref="SendHttpRequest"/> activity's <see cref="SendHttpRequest.ContentType"/> property.
/// Provides options for the <see cref="SendHttpRequest"/> activity's <see cref="SendHttpRequestBase.ContentType"/> property.
/// </summary>
public class HttpContentTypeOptionsProvider : DropDownOptionsProviderBase
{

View file

@ -10,7 +10,7 @@ namespace Elsa.Extensions;
public static class ModuleExtensions
{
/// <summary>
/// Installs & configures the <see cref="IdentityFeature"/> feature.
/// Installs and configures the <see cref="IdentityFeature"/> feature.
/// </summary>
public static IModule UseIdentity(this IModule module, Action<IdentityFeature>? configure = null)
{
@ -19,7 +19,7 @@ public static class ModuleExtensions
}
/// <summary>
/// Installs & configures the <see cref="IdentityFeature"/> feature.
/// Installs and configures the <see cref="IdentityFeature"/> feature.
/// </summary>
public static IModule UseIdentity(this IModule module, string signingKey, string issuer = "http://elsa.api", string audience = "http://elsa.api", TimeSpan? tokenLifetime = null)
{
@ -34,7 +34,7 @@ public static class ModuleExtensions
}
/// <summary>
/// Installs & configures the <see cref="IdentityFeature"/> feature.
/// Installs and configures the <see cref="DefaultAuthenticationFeature"/> feature.
/// </summary>
public static IModule UseDefaultAuthentication(this IModule module, Action<DefaultAuthenticationFeature>? configure = null)
{

View file

@ -1,4 +1,5 @@
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -10,6 +11,7 @@ namespace Elsa.Persistence.EFCore;
/// <summary>
/// Class That enable Schema change for Migration
/// </summary>
[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Required to customize migration assembly behavior for schema-aware migrations.")]
public class DbSchemaAwareMigrationAssembly(
ICurrentDbContext currentContext,
IDbContextOptions options,

View file

@ -5,6 +5,7 @@
Provides common Entity Framework Core types for implementing EF Core persistence of varipus ELsa modules.
</Description>
<PackageTags>elsa extension module persistence efcore</PackageTags>
<RootNamespace>Elsa.Persistence.EFCore</RootNamespace>
</PropertyGroup>
<ItemGroup>

View file

@ -4,7 +4,7 @@ using Elsa.Workflows.Models;
namespace Elsa.Resilience.Modifiers;
/// <summary>
/// Modifies the <see cref="ActivityDescriptor"/> of the <see cref="Cron"/> activity.
/// Modifies the <see cref="ActivityDescriptor"/> for resilient activities.
/// </summary>
public class ResilientActivityDescriptorModifier : IActivityDescriptorModifier
{

View file

@ -2,6 +2,6 @@ namespace Elsa.Workflows.Api.Endpoints.ActivityDescriptors.Get;
internal class Request
{
public string TypeName { get; set; }
public string TypeName { get; set; } = null!;
public int? Version { get; set; }
}

View file

@ -36,7 +36,7 @@ internal class List(IWorkflowInstanceVariableManager workflowInstanceVariableMan
internal class Request
{
public ICollection<VariableUpdateValue> Variables { get; set; }
public ICollection<VariableUpdateValue> Variables { get; set; } = null!;
}
internal record ResolvedVariableModel(string Id, string Name, object? Value);

View file

@ -7,4 +7,5 @@ namespace Elsa.Workflows.Api.Models;
/// <param name="ActivityId">The ID of the activity.</param>
/// <param name="ActivityType">The type of the activity.</param>
/// <param name="PortName">The name of the port.</param>
/// <param name="ActivityName">The name of the activity.</param>
public record ActivityPathSegment(string ActivityNodeId, string ActivityId, string ActivityType, string PortName, string ActivityName);

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
@ -45,6 +46,7 @@ public class ArgumentJsonConverter : JsonConverter<ArgumentDefinition>
}
/// <inheritdoc />
[UnconditionalSuppressMessage("Trimming", "IL2055:Call to MakeGenericType can not be statically analyzed", Justification = "Types are dynamically resolved from workflow definitions and registered in the well-known type registry.")]
public override ArgumentDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var jsonObject = (JsonObject)JsonNode.Parse(ref reader)!;

View file

@ -63,7 +63,6 @@ public partial class Flowchart
/// This method determines whether an activity should be scheduled based on visited connections,
/// forward traversal rules, and backward connections.
/// </summary>
/// <param name="flowchart">The flowchart containing the activities.</param>
/// <param name="flowGraph">The graph representation of the flowchart.</param>
/// <param name="flowScope">Tracks activity and connection visits.</param>
/// <param name="flowchartContext">The execution context of the flowchart.</param>

View file

@ -9,8 +9,9 @@ using Elsa.Workflows.Models;
namespace Elsa.Workflows.Activities;
/// <summary>
/// A strongly-typed for-each construct where <see cref="T"/> is the item type.
/// A strongly-typed for-each construct where <typeparamref name="T"/> is the item type.
/// </summary>
/// <typeparam name="T">The type of items in the collection to iterate over.</typeparam>
public class ForEach<T> : Activity
{
private const string CurrentIndexProperty = "CurrentIndex";

View file

@ -131,7 +131,6 @@ public interface IWorkflowStateSerializer
/// Deserializes the specified serialized state.
/// </summary>
/// <param name="serializedState">The serialized state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The deserialized workflow state.</returns>
[RequiresUnreferencedCode("The type 'T' may be trimmed from the output. The deserialization process may require access to the type.")]
T Deserialize<T>(string serializedState);

View file

@ -15,7 +15,7 @@ public static class MemoryBlockReferenceExtensions
/// </summary>
/// <param name="context">The <see cref="ExpressionExecutionContext"/> to get the value from.</param>
/// <param name="blockId">The ID of the memory block to get the value for.</param>
/// <returns>The value of the memory reference referenced by the specified <see cref="blockId"/>.</returns>
/// <returns>The value of the memory reference referenced by the specified block ID.</returns>
public static object? Get(this ExpressionExecutionContext context, string blockId)
{
var matchingContext = context.FindContextContainingBlock(blockId) ?? context;
@ -27,7 +27,7 @@ public static class MemoryBlockReferenceExtensions
/// </summary>
/// <param name="context">The <see cref="ActivityExecutionContext"/> to get the value from.</param>
/// <param name="blockId">The ID of the memory block to get the value for.</param>
/// <returns>The value of the memory reference referenced by the specified <see cref="blockId"/>.</returns>
/// <returns>The value of the memory reference referenced by the specified block ID.</returns>
public static object? Get(this ActivityExecutionContext context, string blockId) => context.ExpressionExecutionContext.Get(blockId);
/// <summary>

View file

@ -15,6 +15,7 @@ namespace Elsa.Workflows.Models;
/// <param name="CreatedAt">The date and time the bookmark was created.</param>
/// <param name="AutoBurn">Whether or not the bookmark should be automatically burned.</param>
/// <param name="CallbackMethodName">The name of the method on the activity class to invoke when the bookmark is resumed.</param>
/// <param name="AutoComplete">Whether or not the activity should be automatically completed when the bookmark is resumed.</param>
/// <param name="Metadata">Custom properties associated with the bookmark.</param>
public record Bookmark(
string Id,

View file

@ -8,7 +8,7 @@ public class CheckList
/// <summary>
/// The radio list.
/// </summary>
public IEnumerable<CheckListItem> Items { get; set; }
public IEnumerable<CheckListItem> Items { get; set; } = null!;
/// <summary>
/// The name of the provider that will provide the select list.

View file

@ -8,7 +8,7 @@ public class RadioList
/// <summary>
/// The radio list.
/// </summary>
public IEnumerable<RadioListItem> Items { get; set; }
public IEnumerable<RadioListItem> Items { get; set; } = null!;
/// <summary>
/// The name of the provider that will provide the select list.

View file

@ -60,8 +60,8 @@ public class WorkflowDefinitionModel
Root = root;
}
public string Id { get; set; }
public string DefinitionId { get; set; }
public string Id { get; set; } = null!;
public string DefinitionId { get; set; } = null!;
public string? TenantId { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }

View file

@ -9,5 +9,6 @@ namespace Elsa.Workflows.Management.Notifications;
/// A notification that is sent when a workflow definition is published.
/// </summary>
/// <param name="WorkflowDefinition">The workflow definition.</param>
/// <param name="AffectedWorkflows">The affected workflows.</param>
[PublicAPI]
public record WorkflowDefinitionPublished(WorkflowDefinition WorkflowDefinition, AffectedWorkflows AffectedWorkflows) : INotification;

View file

@ -8,4 +8,5 @@ namespace Elsa.Workflows.Management.Notifications;
/// A request to validate a workflow definition.
/// </summary>
/// <param name="Workflow">The workflow materialized from the definition.</param>
/// <param name="ValidationErrors">The collection of validation errors.</param>
public record WorkflowDefinitionValidating(Workflow Workflow, ICollection<WorkflowValidationError> ValidationErrors) : INotification;

View file

@ -59,7 +59,7 @@ public class DispatchWorkflow : Activity<object>
/// Indicates whether a new trace context should be started for the workflow execution.
/// </summary>
[Input(Description = "Start a new trace context when using Open Telemetry.", Category = "Open Telemetry")]
public Input<bool> StartNewTrace { get; set; }
public Input<bool> StartNewTrace { get; set; } = null!;
/// <summary>
/// The channel to dispatch the workflow to.

View file

@ -15,7 +15,7 @@ public interface IWorkflowDefinitionStorePopulator
Task<IEnumerable<WorkflowDefinition>> PopulateStoreAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Populates the <see cref="IWorkflowDefinitionStore"/> with workflow definitions provided from <see cref="IWorkflowProvider"/> implementations.
/// Populates the <see cref="IWorkflowDefinitionStore"/> with workflow definitions provided from <see cref="IWorkflowsProvider"/> implementations.
/// </summary>
/// <param name="indexTriggers">Whether to index triggers.</param>
/// <param name="cancellationToken">The cancellation token.</param>

View file

@ -105,12 +105,12 @@ public class WorkflowRuntimeFeature(IModule module) : FeatureBase(module)
public Func<IServiceProvider, IBackgroundActivityScheduler> BackgroundActivityScheduler { get; set; } = sp => ActivatorUtilities.CreateInstance<LocalBackgroundActivityScheduler>(sp);
/// <summary>
/// A factory that instantiates an <see cref="ILogRecordSink"/> for an <see cref="ActivityExecutionRecord"/>.
/// A factory that instantiates a log record sink for an <see cref="ActivityExecutionRecord"/>.
/// </summary>
public Func<IServiceProvider, ILogRecordSink<ActivityExecutionRecord>> ActivityExecutionLogSink { get; set; } = sp => sp.GetRequiredService<StoreActivityExecutionLogSink>();
/// <summary>
/// A factory that instantiates an <see cref="ILogRecordSink"/> for an <see cref="WorkflowExecutionLogRecord"/>.
/// A factory that instantiates a log record sink for an <see cref="WorkflowExecutionLogRecord"/>.
/// </summary>
public Func<IServiceProvider, ILogRecordSink<WorkflowExecutionLogRecord>> WorkflowExecutionLogSink { get; set; } = sp => sp.GetRequiredService<StoreWorkflowExecutionLogSink>();

View file

@ -3,14 +3,14 @@ using Elsa.Workflows.Models;
namespace Elsa.Workflows.Runtime.Notifications;
/// <summary>
/// Contains the bookmarks that were added, removed, or unchanged.
/// Contains the bookmarks that were added, removed, or unchanged.
/// </summary>
/// <param name="InstanceId">The workflow instance ID.</param>
/// <param name="WorkflowExecutionContext">The workflow execution context.</param>
/// <param name="AddedBookmarks">The bookmarks that were added.</param>
/// <param name="RemovedBookmarks">The bookmarks that were removed.</param>
/// <param name="UnchangedBookmarks">The bookmarks that were unchanged.</param>
public record IndexedWorkflowBookmarks(
WorkflowExecutionContext WorkflowExecutionContext,
ICollection<Bookmark> AddedBookmarks,
ICollection<Bookmark> RemovedBookmarks,
ICollection<Bookmark> AddedBookmarks,
ICollection<Bookmark> RemovedBookmarks,
ICollection<Bookmark> UnchangedBookmarks);

View file

@ -5,7 +5,7 @@ namespace Elsa.Workflows.Runtime.Notifications;
/// <summary>
/// A domain event that applications can subscribe to in order to handle tasks requested by a workflow.
/// </summary>
/// <param>name="ActivityExecutionContext">The context of the activity that requested the task to run.</param>
/// <param name="ActivityExecutionContext">The context of the activity that requested the task to run.</param>
/// <param name="TaskId">A unique identifier for an individual task request.</param>
/// <param name="TaskName">The name of the task requested to run.</param>
/// <param name="TaskPayload">Any additional parameters to send to the task.</param>

View file

@ -5,7 +5,6 @@ namespace Elsa.Workflows.Runtime.Notifications;
/// <summary>
/// A notification that is sent when the bookmarks of a workflow instance have been indexed.
/// </summary>
/// <param name="WorkflowExecutionContext">The workflow execution context.</param>
/// <param name="IndexedWorkflowBookmarks">The bookmarks that were added, removed, or unchanged.</param>
public record WorkflowBookmarksIndexed(IndexedWorkflowBookmarks IndexedWorkflowBookmarks) : INotification;

View file

@ -7,6 +7,5 @@ namespace Elsa.Workflows.Runtime.Notifications;
/// <summary>
/// Published when the bookmarks of a workflow instance have been persisted.
/// </summary>
/// <param name="Context">The workflow execution context.</param>
/// <param name="Diff">The bookmarks that were added, removed, or unchanged.</param>
public record WorkflowBookmarksPersisted(Diff<Bookmark> Diff) : INotification;

View file

@ -6,7 +6,7 @@ namespace Elsa.Workflows.Runtime.Requests;
/// <summary>
/// Published when bookmarks needs to be updated.
/// </summary>
/// <param name="WorkflowInstanceId">The workflow instance ID.</param>
/// <param name="WorkflowExecutionContext">The workflow execution context.</param>
/// <param name="Diff">A diff of the bookmarks.</param>
/// <param name="CorrelationId">The correlation ID, if any.</param>
public record UpdateBookmarksRequest(WorkflowExecutionContext WorkflowExecutionContext, Diff<Bookmark> Diff, string? CorrelationId = default);

View file

@ -1,3 +1,4 @@
using Elsa.Common.Models;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;

View file

@ -9,6 +9,7 @@ namespace Elsa.Workflows.Runtime;
/// Validates the workflow request before dispatching it to the workflow dispatcher.
/// </summary>
/// <param name="decoratedService">The workflow dispatcher to decorate.</param>
/// <param name="dispatcherOptions">The workflow dispatcher options.</param>
public class ValidatingWorkflowDispatcher(IWorkflowDispatcher decoratedService, IOptions<WorkflowDispatcherOptions> dispatcherOptions) : IWorkflowDispatcher
{
private IWorkflowDispatcher DecoratedService { get; set; } = decoratedService;

View file

@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.IO.Compression;
using Elsa.Common;
@ -29,9 +30,9 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.ContentType = new Input<string?>(contentType);
activity.Filename = new Input<string?>(filename);
activity.Content = new(testContent);
activity.ContentType = new(contentType);
activity.Filename = new(filename);
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -49,8 +50,8 @@ public class WriteFileHttpResponseTests
{
// Arrange
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
activity.Content = new Input<object>(content);
activity.Filename = new Input<string?>("test.txt");
activity.Content = new(content);
activity.Filename = new("test.txt");
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -68,8 +69,8 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testBytes = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testBytes);
activity.Filename = new Input<string?>("test.bin");
activity.Content = new(testBytes);
activity.Filename = new("test.bin");
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -88,8 +89,8 @@ public class WriteFileHttpResponseTests
var testContent = "Stream content";
var stream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
activity.Content = new Input<object>(stream);
activity.Filename = new Input<string?>("stream.txt");
activity.Content = new(stream);
activity.Filename = new("stream.txt");
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -107,8 +108,8 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testUri = new Uri("https://example.com/file.txt");
activity.Content = new Input<object>(testUri);
activity.Filename = new Input<string?>("downloaded.txt");
activity.Content = new(testUri);
activity.Filename = new("downloaded.txt");
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -129,8 +130,8 @@ public class WriteFileHttpResponseTests
"File 2 content"u8.ToArray()
};
activity.Content = new Input<object>(files);
activity.Filename = new Input<string?>("archive.zip");
activity.Content = new(files);
activity.Filename = new("archive.zip");
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -146,7 +147,7 @@ public class WriteFileHttpResponseTests
{
// Arrange
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
activity.Content = new Input<object>((object?)null!, string.Empty);
activity.Content = new((object?)null!, string.Empty);
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -161,7 +162,7 @@ public class WriteFileHttpResponseTests
{
// Arrange
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
activity.Content = new Input<object>(Array.Empty<object>());
activity.Content = new(Array.Empty<object>());
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -181,9 +182,9 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.EntityTag = new Input<string?>(entityTag);
activity.EnableResumableDownloads = new Input<bool>(true);
activity.Content = new(testContent);
activity.EntityTag = new(entityTag);
activity.EnableResumableDownloads = new(true);
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -202,9 +203,9 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.EnableResumableDownloads = new Input<bool>(true);
activity.EntityTag = new Input<string?>("\"test-etag\"");
activity.Content = new(testContent);
activity.EnableResumableDownloads = new(true);
activity.EntityTag = new("\"test-etag\"");
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -223,9 +224,9 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.EnableResumableDownloads = new Input<bool>(false);
activity.EntityTag = new Input<string?>("\"test-etag\"");
activity.Content = new(testContent);
activity.EnableResumableDownloads = new(false);
activity.EntityTag = new("\"test-etag\"");
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -247,8 +248,8 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.Filename = new Input<string?>(filename);
activity.Content = new(testContent);
activity.Filename = new(filename);
// ContentType not set, should be determined from filename
// Act
@ -266,7 +267,7 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.Content = new(testContent);
// Filename not set, should default to "file.bin"
// Act
@ -306,7 +307,7 @@ public class WriteFileHttpResponseTests
// Arrange
var activity = new WriteFileHttpResponse();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.Content = new(testContent);
var fixture = new ActivityTestFixture(activity);
var httpContext = CreateMockHttpContext();
@ -361,7 +362,7 @@ public class WriteFileHttpResponseTests
ETag = "\"metadata-etag\""
};
activity.Content = new Input<object>(downloadable);
activity.Content = new(downloadable);
// Act
var context = await ExecuteActivityAsync(activity, httpContext);
@ -379,9 +380,9 @@ public class WriteFileHttpResponseTests
var (activity, httpContext) = CreateWriteFileHttpResponseActivity();
var testContent = "Hello World"u8.ToArray();
activity.Content = new Input<object>(testContent);
activity.EntityTag = new Input<string?>("W/\"weak-etag\""); // Malformed weak ETag format
activity.EnableResumableDownloads = new Input<bool>(true);
activity.Content = new(testContent);
activity.EntityTag = new("W/\"weak-etag\""); // Malformed weak ETag format
activity.EnableResumableDownloads = new(true);
// Act & Assert
var exception = await Assert.ThrowsAsync<FormatException>(async () =>
@ -548,14 +549,14 @@ public class WriteFileHttpResponseTests
}
else
{
services.AddSingleton<object>(_ => new object());
services.AddSingleton<object>(_ => new());
}
}
private static object CreateZipManagerInstance(IServiceProvider serviceProvider)
{
var zipManagerType = typeof(WriteFileHttpResponse).Assembly.GetType("Elsa.Http.Services.ZipManager");
if (zipManagerType == null) return new object();
if (zipManagerType == null) return new();
try
{
@ -591,13 +592,14 @@ public class WriteFileHttpResponseTests
};
}
[UnconditionalSuppressMessage("Trimming", "IL2060:Call to MakeGenericMethod can not be statically analyzed", Justification = "Test helper method that creates mocks dynamically for any logger type.")]
private static object CreateLoggerMock(Type loggerType)
{
try
{
var mockLoggerMethod = typeof(Substitute).GetMethods()
.FirstOrDefault(m => m is { Name: "For", IsGenericMethodDefinition: true } && m.GetParameters().Length == 0);
if (mockLoggerMethod != null)
{
var genericMethod = mockLoggerMethod.MakeGenericMethod(loggerType);
@ -634,7 +636,7 @@ public class WriteFileHttpResponseTests
{
// Simplified mock creation for different content types
case byte[] byteArray:
yield return new Downloadable
yield return new()
{
Stream = new MemoryStream(byteArray),
ContentType = null,
@ -642,7 +644,7 @@ public class WriteFileHttpResponseTests
};
break;
case string text:
yield return new Downloadable
yield return new()
{
Stream = new MemoryStream(Encoding.UTF8.GetBytes(text)),
ContentType = null,
@ -651,7 +653,7 @@ public class WriteFileHttpResponseTests
break;
case Stream stream:
// For Stream objects, return them as-is
yield return new Downloadable
yield return new()
{
Stream = stream,
ContentType = null,
@ -662,7 +664,7 @@ public class WriteFileHttpResponseTests
{
// For Uri objects, simulate downloaded content
var mockContent = $"Downloaded content from {uri}";
yield return new Downloadable
yield return new()
{
Stream = new MemoryStream(Encoding.UTF8.GetBytes(mockContent)),
ContentType = null,
@ -710,7 +712,7 @@ public class WriteFileHttpResponseTests
}
memoryStream.Position = 0;
return new Downloadable
return new()
{
Stream = memoryStream,
ContentType = "application/zip",