elsa-core/test/unit/Elsa.Workflows.Api.UnitTests/OutputConverters/OutputConverterEndpointTests.cs
Sipke Schoorstra 61fc376dac
Add output converter support at binding boundaries
Backport to release/3.8.0 so Elsa.Api.Client 3.8.0-rc2 exposes the
Resources/OutputConverters surface that Elsa Studio's release/3.8.0 branch
already consumes. Without it, Studio cannot build against a released client:
it was green against 3.8.0-preview.5397 (built from main) and broke when its
pin moved to 3.8.0-rc1 (built from this branch).

(cherry picked from commit d698e6b005)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 22:22:46 +02:00

78 lines
3.9 KiB
C#

using System.Text.Json;
using Elsa.Common.Serialization;
using Elsa.Models;
using Elsa.Workflows.Api.Endpoints.OutputConverters.List;
using Elsa.Workflows.Models;
using FastEndpoints;
using NSubstitute;
namespace Elsa.Workflows.Api.UnitTests.OutputConverters;
public class OutputConverterEndpointTests
{
[Fact]
public void Configure_ExposesTheAuthorizedDescriptorRoute()
{
var endpoint = new List(Substitute.For<IOutputConverterRegistry>(), SerializationTypeRegistry.CreateDefault());
var definition = new EndpointDefinition(typeof(List), typeof(EmptyRequest), typeof(ListResponse<OutputConverterDescriptorModel>));
typeof(List).GetProperty("Definition", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)!.SetValue(endpoint, definition);
endpoint.Configure();
Assert.Contains("/descriptors/output-converters", definition.Routes);
var permissions = (IEnumerable<string>)typeof(EndpointDefinition)
.GetProperty("AllowedPermissions", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)!
.GetValue(definition)!;
Assert.Contains("read:*", permissions);
Assert.Contains("read:output-converters", permissions);
}
[Fact]
public void ListCompatible_FiltersThroughTheRegistryAndExposesOnlySafeDescriptorMetadata()
{
using var document = JsonDocument.Parse("""{"type":"object","properties":{"format":{"type":"string"}}}""");
var descriptor = new OutputConverterDescriptor(
"sample.to-text",
typeof(string),
typeof(string),
"Convert to text",
"Formats the source as text.",
document.RootElement);
var registry = Substitute.For<IOutputConverterRegistry>();
registry.FindCompatible(typeof(string), typeof(int)).Returns([descriptor]);
var endpoint = new List(registry, SerializationTypeRegistry.CreateDefault());
var listed = endpoint.TryListCompatible("String", "Int32", out var response, out var errors);
var model = Assert.Single(response.Items);
Assert.True(listed);
Assert.Empty(errors);
registry.Received(1).FindCompatible(typeof(string), typeof(int));
Assert.Equal("sample.to-text", model.Id);
Assert.Equal("String", model.SourceTypeName);
Assert.Equal("String", model.ResultTypeName);
Assert.Equal("Convert to text", model.DisplayName);
Assert.Equal("Formats the source as text.", model.Description);
Assert.Equal("object", model.SettingsSchema!.Value.GetProperty("type").GetString());
Assert.DoesNotContain(typeof(OutputConverterDescriptorModel).GetProperties(), property => property.Name is "SourceType" or "ResultType" or "ServiceKey" or "ServiceLifetime");
}
[Theory]
[InlineData(null, "String", "The sourceType query parameter is required.")]
[InlineData("String", null, "The destinationType query parameter is required.")]
[InlineData("Unsafe.Type", "String", "The sourceType query parameter must be a registered type alias or resolvable safe type name.")]
[InlineData("String", "Unsafe.Type", "The destinationType query parameter must be a registered type alias or resolvable safe type name.")]
public void TryListCompatible_RejectsMissingOrUnsafeQueryTypes(string? sourceTypeName, string? destinationTypeName, string expectedError)
{
var registry = Substitute.For<IOutputConverterRegistry>();
var endpoint = new List(registry, SerializationTypeRegistry.CreateDefault());
var listed = endpoint.TryListCompatible(sourceTypeName, destinationTypeName, out var response, out var errors);
Assert.False(listed);
Assert.Empty(response.Items);
Assert.Contains(expectedError, errors);
registry.DidNotReceive().FindCompatible(Arg.Any<Type>(), Arg.Any<Type>());
}
}