diff --git a/src/Altinn.App.Api/Controllers/Attributes/JsonSettingsNameAttribute.cs b/src/Altinn.App.Api/Controllers/Attributes/JsonSettingsNameAttribute.cs
new file mode 100644
index 000000000..57e6fe199
--- /dev/null
+++ b/src/Altinn.App.Api/Controllers/Attributes/JsonSettingsNameAttribute.cs
@@ -0,0 +1,19 @@
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace Altinn.App.Api.Controllers.Attributes;
+
+[AttributeUsage(AttributeTargets.Class)]
+internal class JsonSettingsNameAttribute : Attribute, IFilterMetadata
+{
+ internal JsonSettingsNameAttribute(string name)
+ {
+ Name = name;
+ }
+
+ internal string Name { get; }
+}
+
+internal static class JsonSettingNames
+{
+ internal const string AltinnApi = "AltinnApi";
+}
diff --git a/src/Altinn.App.Api/Controllers/Conventions/AltinnApiJsonFormatter.cs b/src/Altinn.App.Api/Controllers/Conventions/AltinnApiJsonFormatter.cs
new file mode 100644
index 000000000..64da93ef3
--- /dev/null
+++ b/src/Altinn.App.Api/Controllers/Conventions/AltinnApiJsonFormatter.cs
@@ -0,0 +1,44 @@
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using Altinn.App.Api.Extensions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Altinn.App.Api.Controllers.Conventions;
+
+internal sealed class AltinnApiJsonFormatter : SystemTextJsonOutputFormatter
+{
+ private AltinnApiJsonFormatter(string settingsName, JsonSerializerOptions options)
+ : base(options)
+ {
+ SettingsName = settingsName;
+ }
+
+ internal string SettingsName { get; }
+
+ public override bool CanWriteResult(OutputFormatterCanWriteContext context)
+ {
+ if (context.HttpContext.GetJsonSettingsName() != SettingsName)
+ {
+ return false;
+ }
+
+ return base.CanWriteResult(context);
+ }
+
+ internal static AltinnApiJsonFormatter CreateFormatter(string settingsName, JsonOptions jsonOptions)
+ {
+ var jsonSerializerOptions = jsonOptions.JsonSerializerOptions;
+
+ if (jsonSerializerOptions.Encoder is null)
+ {
+ // If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters.
+ jsonSerializerOptions = new JsonSerializerOptions(jsonSerializerOptions)
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ };
+ }
+
+ return new AltinnApiJsonFormatter(settingsName, jsonSerializerOptions);
+ }
+}
diff --git a/src/Altinn.App.Api/Controllers/Conventions/AltinnControllerConventions.cs b/src/Altinn.App.Api/Controllers/Conventions/AltinnControllerConventions.cs
new file mode 100644
index 000000000..d091dfc80
--- /dev/null
+++ b/src/Altinn.App.Api/Controllers/Conventions/AltinnControllerConventions.cs
@@ -0,0 +1,12 @@
+using Altinn.App.Api.Controllers.Attributes;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+
+namespace Altinn.App.Api.Controllers.Conventions;
+
+internal class AltinnControllerConventions : IControllerModelConvention
+{
+ public void Apply(ControllerModel controller)
+ {
+ controller.Filters.Add(new JsonSettingsNameAttribute(JsonSettingNames.AltinnApi));
+ }
+}
diff --git a/src/Altinn.App.Api/Controllers/Conventions/ConfigureMvcJsonOptions.cs b/src/Altinn.App.Api/Controllers/Conventions/ConfigureMvcJsonOptions.cs
new file mode 100644
index 000000000..4027c774a
--- /dev/null
+++ b/src/Altinn.App.Api/Controllers/Conventions/ConfigureMvcJsonOptions.cs
@@ -0,0 +1,45 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Formatters;
+using Microsoft.Extensions.Options;
+
+namespace Altinn.App.Api.Controllers.Conventions;
+
+///
+/// Configures MVC options to use a specific JSON serialization settings for enum-to-number conversion.
+///
+public class ConfigureMvcJsonOptions : IConfigureOptions
+{
+ private readonly string _jsonSettingsName;
+ private readonly IOptionsMonitor _jsonOptions;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the JSON settings to be used for enum-to-number conversion.
+ /// /// An to access the named JSON options.
+ public ConfigureMvcJsonOptions(string jsonSettingsName, IOptionsMonitor jsonOptions)
+ {
+ _jsonSettingsName = jsonSettingsName;
+ _jsonOptions = jsonOptions;
+ }
+
+ ///
+ /// Configures the MVC options to use the for the specified JSON settings.
+ /// Makes sure to add to the formatter before the default .
+ ///
+ /// The to configure.
+ public void Configure(MvcOptions options)
+ {
+ var defaultJsonFormatter =
+ options.OutputFormatters.OfType().FirstOrDefault()
+ ?? throw new InvalidOperationException("Could not find the default JSON output formatter");
+
+ var indexOfDefaultJsonFormatter = options.OutputFormatters.IndexOf(defaultJsonFormatter);
+
+ var jsonOptions = _jsonOptions.Get(_jsonSettingsName);
+ options.OutputFormatters.Insert(
+ indexOfDefaultJsonFormatter,
+ AltinnApiJsonFormatter.CreateFormatter(_jsonSettingsName, jsonOptions)
+ );
+ }
+}
diff --git a/src/Altinn.App.Api/Extensions/HttpContextExtensions.cs b/src/Altinn.App.Api/Extensions/HttpContextExtensions.cs
new file mode 100644
index 000000000..1609eef43
--- /dev/null
+++ b/src/Altinn.App.Api/Extensions/HttpContextExtensions.cs
@@ -0,0 +1,11 @@
+using Altinn.App.Api.Controllers.Attributes;
+
+namespace Altinn.App.Api.Extensions;
+
+internal static class HttpContextExtensions
+{
+ internal static string? GetJsonSettingsName(this HttpContext context)
+ {
+ return context.GetEndpoint()?.Metadata.GetMetadata()?.Name;
+ }
+}
diff --git a/src/Altinn.App.Api/Extensions/MvcBuilderExtensions.cs b/src/Altinn.App.Api/Extensions/MvcBuilderExtensions.cs
new file mode 100644
index 000000000..877e8f429
--- /dev/null
+++ b/src/Altinn.App.Api/Extensions/MvcBuilderExtensions.cs
@@ -0,0 +1,28 @@
+using Altinn.App.Api.Controllers.Conventions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+
+namespace Altinn.App.Api.Extensions;
+
+internal static class MvcBuilderExtensions
+{
+ internal static IMvcBuilder AddJsonOptions(
+ this IMvcBuilder builder,
+ string settingsName,
+ Action configure
+ )
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configure);
+
+ builder.Services.Configure(settingsName, configure);
+
+ builder.Services.AddSingleton>(sp =>
+ {
+ var options = sp.GetRequiredService>();
+ return new ConfigureMvcJsonOptions(settingsName, options);
+ });
+
+ return builder;
+ }
+}
diff --git a/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs
index 974be3360..a014ed7eb 100644
--- a/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs
@@ -1,5 +1,7 @@
using System.Diagnostics;
using Altinn.App.Api.Controllers;
+using Altinn.App.Api.Controllers.Attributes;
+using Altinn.App.Api.Controllers.Conventions;
using Altinn.App.Api.Helpers;
using Altinn.App.Api.Infrastructure.Filters;
using Altinn.App.Api.Infrastructure.Health;
@@ -43,10 +45,18 @@ public static void AddAltinnAppControllersWithViews(this IServiceCollection serv
IMvcBuilder mvcBuilder = services.AddControllersWithViews(options =>
{
options.Filters.Add();
+ options.Conventions.Add(new AltinnControllerConventions());
});
mvcBuilder
.AddApplicationPart(typeof(InstancesController).Assembly)
.AddXmlSerializerFormatters()
+ .AddJsonOptions(
+ JsonSettingNames.AltinnApi,
+ options =>
+ {
+ options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
+ }
+ )
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
diff --git a/test/Altinn.App.Api.Tests/Controllers/Conventions/AltinnApiJsonFormatterTests.cs b/test/Altinn.App.Api.Tests/Controllers/Conventions/AltinnApiJsonFormatterTests.cs
new file mode 100644
index 000000000..617e992db
--- /dev/null
+++ b/test/Altinn.App.Api.Tests/Controllers/Conventions/AltinnApiJsonFormatterTests.cs
@@ -0,0 +1,105 @@
+using System.Text.Encodings.Web;
+using System.Text.Json.Serialization.Metadata;
+using Altinn.App.Api.Controllers.Attributes;
+using Altinn.App.Api.Controllers.Conventions;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Altinn.App.Api.Tests.Controllers.Conventions;
+
+public class AltinnApiJsonFormatterTests
+{
+ [Fact]
+ public void CreateFormatter_WhenEncoderIsNotNull_PreservesEncoder()
+ {
+ // Arrange
+ string settingsName = JsonSettingNames.AltinnApi;
+ var originalEncoder = JavaScriptEncoder.Default;
+
+ var jsonOptions = new JsonOptions();
+ jsonOptions.JsonSerializerOptions.Encoder = originalEncoder;
+ jsonOptions.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ // Act
+ var formatter = AltinnApiJsonFormatter.CreateFormatter(settingsName, jsonOptions);
+
+ // Assert
+ Assert.NotNull(formatter);
+ Assert.Equal(settingsName, formatter.SettingsName);
+ Assert.Equal(originalEncoder, formatter.SerializerOptions.Encoder);
+ }
+
+ [Fact]
+ public void CanWriteResult_SettingsNameMatches_ReturnsTrue()
+ {
+ // Arrange
+ string settingsName = JsonSettingNames.AltinnApi;
+
+ var jsonOptions = new JsonOptions();
+ jsonOptions.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ var formatter = AltinnApiJsonFormatter.CreateFormatter(settingsName, jsonOptions);
+
+ var httpContext = new DefaultHttpContext();
+
+ // Create an Endpoint with JsonSettingsNameAttribute
+ var endpoint = new Endpoint(
+ requestDelegate: null,
+ metadata: new EndpointMetadataCollection(new JsonSettingsNameAttribute(settingsName)),
+ displayName: null
+ );
+
+ httpContext.SetEndpoint(endpoint);
+
+ var context = new OutputFormatterWriteContext(
+ httpContext,
+ (stream, encoding) => new StreamWriter(stream, encoding),
+ typeof(object),
+ new object()
+ );
+
+ // Act
+ bool canWrite = formatter.CanWriteResult(context);
+
+ // Assert
+ Assert.True(canWrite);
+ }
+
+ [Fact]
+ public void CanWriteResult_SettingsNameMisMatch_ReturnsFalse()
+ {
+ // Arrange
+ string formatterSettingsName = "FormatterSettingName";
+ string endpointSettingsName = "EndpointSettingName";
+
+ var jsonOptions = new JsonOptions();
+ jsonOptions.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ var formatter = AltinnApiJsonFormatter.CreateFormatter(formatterSettingsName, jsonOptions);
+
+ var httpContext = new DefaultHttpContext();
+
+ // Create an Endpoint with JsonSettingsNameAttribute with a different name
+ var endpoint = new Endpoint(
+ requestDelegate: null,
+ metadata: new EndpointMetadataCollection(new JsonSettingsNameAttribute(endpointSettingsName)),
+ displayName: null
+ );
+
+ httpContext.SetEndpoint(endpoint);
+
+ var context = new OutputFormatterWriteContext(
+ httpContext,
+ (stream, encoding) => new StreamWriter(stream, encoding),
+ typeof(object),
+ new object()
+ );
+
+ // Act
+ bool canWrite = formatter.CanWriteResult(context);
+
+ // Assert
+ Assert.False(canWrite);
+ }
+}
diff --git a/test/Altinn.App.Api.Tests/Controllers/Conventions/AltinnControllerConventionTests.cs b/test/Altinn.App.Api.Tests/Controllers/Conventions/AltinnControllerConventionTests.cs
new file mode 100644
index 000000000..c183cc109
--- /dev/null
+++ b/test/Altinn.App.Api.Tests/Controllers/Conventions/AltinnControllerConventionTests.cs
@@ -0,0 +1,31 @@
+using System.Reflection;
+using Altinn.App.Api.Controllers.Attributes;
+using Altinn.App.Api.Controllers.Conventions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ApplicationModels;
+
+namespace Altinn.App.Api.Tests.Controllers.Conventions;
+
+public class AltinnControllerConventionsTests
+{
+ [Fact]
+ public void Apply_AddsJsonSettingsNameAttributeToControllerModel()
+ {
+ // Arrange
+ var convention = new AltinnControllerConventions();
+ var controllerType = typeof(TestController).GetTypeInfo();
+ var controllerModel = new ControllerModel(controllerType, []);
+
+ // Act
+ convention.Apply(controllerModel);
+
+ // Assert
+ var attribute = controllerModel.Filters.OfType().FirstOrDefault();
+
+ Assert.NotNull(attribute);
+ Assert.Equal(JsonSettingNames.AltinnApi, attribute.Name);
+ }
+
+ // Dummy controller
+ private class TestController : ControllerBase { }
+}
diff --git a/test/Altinn.App.Api.Tests/Controllers/Conventions/ConfigureMvcJsonOptionsTests.cs b/test/Altinn.App.Api.Tests/Controllers/Conventions/ConfigureMvcJsonOptionsTests.cs
new file mode 100644
index 000000000..da4340d00
--- /dev/null
+++ b/test/Altinn.App.Api.Tests/Controllers/Conventions/ConfigureMvcJsonOptionsTests.cs
@@ -0,0 +1,96 @@
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+using Altinn.App.Api.Controllers.Attributes;
+using Altinn.App.Api.Controllers.Conventions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Formatters;
+using Microsoft.Extensions.Options;
+
+namespace Altinn.App.Api.Tests.Controllers.Conventions;
+
+public class ConfigureMvcJsonOptionsTests
+{
+ [Fact]
+ public void Configure_InsertsCustomFormatterWithCorrectSettings()
+ {
+ // Arrange
+ var jsonSettingsName = JsonSettingNames.AltinnApi;
+ ConfigureMvcJsonOptions configureOptions = GetConfigureOptionsForTest(jsonSettingsName);
+ var mvcOptions = new MvcOptions();
+
+ // Create default JsonSerializerOptions with JsonStringEnumConverter
+ var defaultSerializerOptions = new JsonSerializerOptions();
+ defaultSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ defaultSerializerOptions.Encoder = JavaScriptEncoder.Default;
+ defaultSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ // Add the default SystemTextJsonOutputFormatter
+ var defaultJsonFormatter = new SystemTextJsonOutputFormatter(defaultSerializerOptions);
+ mvcOptions.OutputFormatters.Add(defaultJsonFormatter);
+
+ // Act
+ configureOptions.Configure(mvcOptions);
+
+ // Assert
+ var customFormatter = mvcOptions.OutputFormatters.OfType().FirstOrDefault();
+
+ Assert.NotNull(customFormatter);
+ Assert.Equal(jsonSettingsName, customFormatter.SettingsName);
+
+ var indexOfDefaultFormatter = mvcOptions.OutputFormatters.IndexOf(defaultJsonFormatter);
+ var indexOfCustomFormatter = mvcOptions.OutputFormatters.IndexOf(customFormatter);
+
+ Assert.Equal(indexOfDefaultFormatter - 1, indexOfCustomFormatter);
+
+ var customSerializerOptions = customFormatter.SerializerOptions;
+ var hasEnumConverter = customSerializerOptions.Converters.Any(c => c is JsonStringEnumConverter);
+
+ Assert.False(
+ hasEnumConverter,
+ "JsonStringEnumConverter should have been removed from the custom formatter's SerializerOptions"
+ );
+
+ Assert.NotNull(customSerializerOptions.Encoder);
+ }
+
+ [Fact]
+ public void Configure_NoDefaultOutputFormatter_ThrowsInvalidOperationException()
+ {
+ ConfigureMvcJsonOptions configureOptions = GetConfigureOptionsForTest(JsonSettingNames.AltinnApi);
+
+ var mvcOptions = new MvcOptions();
+
+ Assert.Throws(() => configureOptions.Configure(mvcOptions));
+ }
+
+ private static ConfigureMvcJsonOptions GetConfigureOptionsForTest(string jsonSettingsName)
+ {
+ var jsonOptions = new JsonOptions();
+ jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+ jsonOptions.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
+
+ var optionsMonitor = new TestOptionsMonitor(jsonOptions);
+ var configureOptions = new ConfigureMvcJsonOptions(jsonSettingsName, optionsMonitor);
+ return configureOptions;
+ }
+}
+
+public class TestOptionsMonitor : IOptionsMonitor
+{
+ public TestOptionsMonitor(TOptions currentValue)
+ {
+ CurrentValue = currentValue;
+ }
+
+ public TOptions CurrentValue { get; }
+
+ public TOptions Get(string? name) => CurrentValue;
+
+ public IDisposable OnChange(Action listener)
+ {
+ // No-op for testing purposes
+ return null!;
+ }
+}
diff --git a/test/Altinn.App.Api.Tests/Controllers/Conventions/EnumSerializationTests.cs b/test/Altinn.App.Api.Tests/Controllers/Conventions/EnumSerializationTests.cs
new file mode 100644
index 000000000..ce6c31f1f
--- /dev/null
+++ b/test/Altinn.App.Api.Tests/Controllers/Conventions/EnumSerializationTests.cs
@@ -0,0 +1,131 @@
+using System.Net;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Altinn.App.Api.Extensions;
+using Altinn.App.Core.Internal.App;
+using Altinn.App.Core.Internal.Auth;
+using Altinn.App.Core.Models;
+using Altinn.Platform.Register.Enums;
+using Altinn.Platform.Storage.Interface.Models;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit.Abstractions;
+
+namespace Altinn.App.Api.Tests.Controllers.Conventions;
+
+public class EnumSerializationTests : ApiTestBase, IClassFixture>
+{
+ private const string Org = "tdd";
+ private const string App = "contributer-restriction";
+ private const int PartyId = 500600;
+
+ private readonly Mock _authorizationClientMock;
+ private readonly Mock _appMetadataMock;
+
+ public EnumSerializationTests(WebApplicationFactory factory, ITestOutputHelper outputHelper)
+ : base(factory, outputHelper)
+ {
+ // Mock auth client to return the enum we want to test
+ _authorizationClientMock = new Mock();
+ _authorizationClientMock
+ .Setup(a => a.GetPartyList(It.IsAny()))
+ .ReturnsAsync([new() { PartyTypeName = PartyType.Person }]);
+
+ _appMetadataMock = new Mock();
+ _appMetadataMock
+ .Setup(s => s.GetApplicationMetadata())
+ .ReturnsAsync(
+ new ApplicationMetadata(id: "ttd/test") { PartyTypesAllowed = new PartyTypesAllowed { Person = true } }
+ );
+
+ OverrideServicesForAllTests = (services) =>
+ {
+ services
+ .AddControllers()
+ .AddJsonOptions(options =>
+ {
+ options.JsonSerializerOptions.Converters.Add(new CustomConverterFactory());
+ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ });
+
+ services.AddScoped(_ => _authorizationClientMock.Object);
+ services.AddScoped(_ => _appMetadataMock.Object);
+ };
+ }
+
+ [Fact]
+ public async Task ValidateInstantiation_SerializesPartyTypesAllowedAsNumber()
+ {
+ // Arrange
+ using var client = GetRootedClient(Org, App, 1337, PartyId);
+
+ // Act
+ var response = await client.PostAsync(
+ $"{Org}/{App}/api/v1/parties/validateInstantiation?partyId={PartyId}",
+ null
+ );
+ response.Should().HaveStatusCode(HttpStatusCode.OK);
+ var content = await response.Content.ReadAsStringAsync();
+
+ var partyTypeEnumJson = JsonDocument
+ .Parse(content)
+ .RootElement.GetProperty("validParties")
+ .EnumerateArray()
+ .First()
+ .GetProperty("partyTypeName");
+
+ // Assert
+ partyTypeEnumJson.Should().NotBeNull();
+ partyTypeEnumJson.TryGetInt32(out var partyTypeJsonValue);
+ partyTypeJsonValue.Should().Be(1, "PartyTypesAllowed should be serialized as its numeric value");
+ }
+}
+
+public class CustomConverterFactory : JsonConverterFactory
+{
+ public override bool CanConvert(Type typeToConvert) => typeToConvert is not null;
+
+ public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+ {
+ var converterType = typeof(CustomConverter<>).MakeGenericType(typeToConvert);
+ return (JsonConverter)Activator.CreateInstance(converterType)!;
+ }
+}
+
+public class CustomConverter : JsonConverter
+{
+ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (typeof(T).IsEnum)
+ {
+ var enumString = reader.GetString();
+
+ if (Enum.TryParse(typeof(T), enumString, out var enumValue))
+ {
+ return (T)enumValue;
+ }
+ else
+ {
+ throw new JsonException($"Unable to convert \"{enumString}\" to enum \"{typeof(T)}\".");
+ }
+ }
+ else
+ {
+ return JsonSerializer.Deserialize(ref reader, options);
+ }
+ }
+
+ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+ {
+ if (value is Enum)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
+ else
+ {
+ JsonSerializer.Serialize(writer, value, value!.GetType(), options);
+ }
+ }
+}
diff --git a/test/Altinn.App.Api.Tests/Extensions/HttpClientExtensionsTests.cs b/test/Altinn.App.Api.Tests/Extensions/HttpClientExtensionsTests.cs
new file mode 100644
index 000000000..ed5c2ddc7
--- /dev/null
+++ b/test/Altinn.App.Api.Tests/Extensions/HttpClientExtensionsTests.cs
@@ -0,0 +1,96 @@
+namespace Altinn.App.Api.Tests.Extensions;
+
+public class HttpClientExtensionsTests
+{
+ [Fact]
+ public void GetDelegatingHandler_HandlerFound_ReturnsHandler()
+ {
+ // Arrange
+ var primaryHandler = new HttpClientHandler();
+ var targetHandler = new CustomDelegatingHandler(primaryHandler);
+ using var httpClient = new HttpClient(targetHandler);
+
+ // Act
+ var result = httpClient.GetDelegatingHandler();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+
+ [Fact]
+ public void GetDelegatingHandler_HandlerNotFound_ReturnsNull()
+ {
+ // Arrange
+ var primaryHandler = new HttpClientHandler();
+ var someOtherHandler = new DelegatingHandlerStub(primaryHandler);
+ using var httpClient = new HttpClient(someOtherHandler);
+
+ // Act
+ var result = httpClient.GetDelegatingHandler();
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void GetDelegatingHandler_InitialHandlerIsNotDelegatingHandler_ReturnsNull()
+ {
+ // Arrange
+ using var httpClient = new HttpClient(new HttpClientHandler());
+
+ // Act
+ var result = httpClient.GetDelegatingHandler();
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void GetDelegatingHandler_InnerHandlerIsNotDelegatingHandler_ReturnsNull()
+ {
+ // Arrange
+ var primaryHandler = new HttpClientHandler();
+ var outerHandler = new DelegatingHandlerStub(primaryHandler);
+ using var httpClient = new HttpClient(outerHandler);
+
+ // Act
+ var result = httpClient.GetDelegatingHandler();
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void GetDelegatingHandler_MultipleHandlersInChain_ReturnsCorrectHandler()
+ {
+ // Arrange
+ var primaryHandler = new HttpClientHandler();
+ var innerHandler = new DelegatingHandlerStub(primaryHandler);
+ var targetHandler = new CustomDelegatingHandler(innerHandler);
+ using var httpClient = new HttpClient(targetHandler);
+
+ // Act
+ var result = httpClient.GetDelegatingHandler();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.IsType(result);
+ }
+}
+
+public class CustomDelegatingHandler : DelegatingHandler
+{
+ public CustomDelegatingHandler(HttpMessageHandler innerHandler)
+ {
+ InnerHandler = innerHandler;
+ }
+}
+
+public class DelegatingHandlerStub : DelegatingHandler
+{
+ public DelegatingHandlerStub(HttpMessageHandler innerHandler)
+ {
+ InnerHandler = innerHandler;
+ }
+}