diff --git a/src/Altinn.App.Core/AppImplementationFactory.cs b/src/Altinn.App.Core/AppImplementationFactory.cs new file mode 100644 index 000000000..6f3c4133e --- /dev/null +++ b/src/Altinn.App.Core/AppImplementationFactory.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Altinn.App.Core.Features; + +internal static class AppImplementationFactoryExtensions +{ + public static IServiceCollection AddAppImplementationFactory(this IServiceCollection services) + { + // Wherever we inject user/app-implemented interfaces, + // we should be in a context where there is a scope + // * ASP.NET Core request scope + // * Background processing scope (i.e. IServiceTask in the future) + // If code tries to resolve an implementation outside of a scope, + // it will throw an exception. + services.AddScoped(); + return services; + } + + public static T GetRequiredAppImplementation(this IServiceProvider sp) + where T : class => sp.GetRequiredService().GetRequiredImplementation(); + + public static T? GetAppImplementation(this IServiceProvider sp) + where T : class => sp.GetRequiredService().GetImplementation(); + + public static IEnumerable GetAppImplementations(this IServiceProvider sp) + where T : class => sp.GetRequiredService().GetImplementations(); +} + +file sealed class AppImplementationFactory +{ + private readonly IServiceProvider _sp; + + public AppImplementationFactory(IServiceProvider serviceProvider) + { + _sp = serviceProvider; + } + + public T GetRequiredImplementation() + where T : class => _sp.GetRequiredService(); + + public T? GetImplementation() + where T : class => _sp.GetService(); + + public IEnumerable GetImplementations() + where T : class => _sp.GetServices(); +} diff --git a/src/Altinn.App.Core/EFormidling/Implementation/DefaultEFormidlingService.cs b/src/Altinn.App.Core/EFormidling/Implementation/DefaultEFormidlingService.cs index b96fd3748..d46dc5659 100644 --- a/src/Altinn.App.Core/EFormidling/Implementation/DefaultEFormidlingService.cs +++ b/src/Altinn.App.Core/EFormidling/Implementation/DefaultEFormidlingService.cs @@ -3,6 +3,7 @@ using Altinn.App.Core.Configuration; using Altinn.App.Core.Constants; using Altinn.App.Core.EFormidling.Interface; +using Altinn.App.Core.Features; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.Auth; using Altinn.App.Core.Internal.Data; @@ -42,13 +43,12 @@ public DefaultEFormidlingService( IUserTokenProvider userTokenProvider, IAppMetadata appMetadata, IDataClient dataClient, - IEFormidlingReceivers eFormidlingReceivers, IEventsClient eventClient, + IServiceProvider sp, IOptions? appSettings = null, IOptions? platformSettings = null, IEFormidlingClient? eFormidlingClient = null, - IAccessTokenGenerator? tokenGenerator = null, - IEFormidlingMetadata? eFormidlingMetadata = null + IAccessTokenGenerator? tokenGenerator = null ) { _logger = logger; @@ -57,10 +57,10 @@ public DefaultEFormidlingService( _platformSettings = platformSettings?.Value; _userTokenProvider = userTokenProvider; _eFormidlingClient = eFormidlingClient; - _eFormidlingMetadata = eFormidlingMetadata; + _eFormidlingMetadata = sp.GetAppImplementation(); + _eFormidlingReceivers = sp.GetRequiredAppImplementation(); _appMetadata = appMetadata; _dataClient = dataClient; - _eFormidlingReceivers = eFormidlingReceivers; _eventClient = eventClient; } diff --git a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs index 27e35d601..48a0374ed 100644 --- a/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs @@ -152,6 +152,8 @@ public static void AddAppServices( IWebHostEnvironment env ) { + services.AddAppImplementationFactory(); + // Services for Altinn App services.TryAddTransient(); AddValidationServices(services, configuration); diff --git a/test/Altinn.App.Core.Tests/Eformidling/Implementation/DefaultEFormidlingServiceTests.cs b/test/Altinn.App.Core.Tests/Eformidling/Implementation/DefaultEFormidlingServiceTests.cs index eed41145a..4e38b6ea4 100644 --- a/test/Altinn.App.Core.Tests/Eformidling/Implementation/DefaultEFormidlingServiceTests.cs +++ b/test/Altinn.App.Core.Tests/Eformidling/Implementation/DefaultEFormidlingServiceTests.cs @@ -1,9 +1,9 @@ -#nullable disable using Altinn.App.Core.Configuration; using Altinn.App.Core.Constants; using Altinn.App.Core.EFormidling; using Altinn.App.Core.EFormidling.Implementation; using Altinn.App.Core.EFormidling.Interface; +using Altinn.App.Core.Features; using Altinn.App.Core.Internal.App; using Altinn.App.Core.Internal.Auth; using Altinn.App.Core.Internal.Data; @@ -14,6 +14,8 @@ using Altinn.Common.EFormidlingClient.Models.SBD; using Altinn.Platform.Storage.Interface.Models; using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; @@ -22,11 +24,34 @@ namespace Altinn.App.Core.Tests.Eformidling.Implementation; public class DefaultEFormidlingServiceTests { - [Fact] - public void SendEFormidlingShipment() + private readonly record struct Fixture( + IServiceProvider ServiceProvider, + Instance Instance, + DefaultEFormidlingService Service + ) : IDisposable { - // Arrange - var logger = new NullLogger(); + public Mock Mock() + where T : class => Moq.Mock.Get(ServiceProvider.GetRequiredService()); + + public void Dispose() + { + if (ServiceProvider is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + private Fixture CreateFixture(Action>? setupEFormidlingClient = null) + { + var services = new ServiceCollection(); + services.AddAppImplementationFactory(); + services.AddLogging(logging => + { + logging.ClearProviders(); + logging.AddProvider(NullLoggerProvider.Instance); + }); + var userTokenProvider = new Mock(); var appMetadata = new Mock(); var dataClient = new Mock(); @@ -73,19 +98,32 @@ public void SendEFormidlingShipment() return ("fakefilename.txt", Stream.Null); }); - var defaultEformidlingService = new DefaultEFormidlingService( - logger, - userTokenProvider.Object, - appMetadata.Object, - dataClient.Object, - eFormidlingReceivers.Object, - eventClient.Object, - appSettings, - platformSettings, - eFormidlingClient.Object, - tokenGenerator.Object, - eFormidlingMetadata.Object - ); + setupEFormidlingClient?.Invoke(eFormidlingClient); + + services.AddTransient(_ => userTokenProvider.Object); + services.AddTransient(_ => appMetadata.Object); + services.AddTransient(_ => dataClient.Object); + services.AddTransient(_ => eFormidlingReceivers.Object); + services.AddTransient(_ => eventClient.Object); + services.AddTransient(_ => appSettings); + services.AddTransient(_ => platformSettings); + services.AddTransient(_ => eFormidlingClient.Object); + services.AddTransient(_ => tokenGenerator.Object); + services.AddTransient(_ => eFormidlingMetadata.Object); + + services.AddTransient(); + + var serviceProvider = services.BuildServiceProvider(); + var service = serviceProvider.GetRequiredService(); + return new(serviceProvider, instance, (DefaultEFormidlingService)service); + } + + [Fact] + public void SendEFormidlingShipment() + { + // Arrange + using var fixture = CreateFixture(); + var (sp, instance, defaultEformidlingService) = fixture; // Act var result = defaultEformidlingService.SendEFormidlingShipment(instance); @@ -98,11 +136,12 @@ public void SendEFormidlingShipment() { General.SubscriptionKeyHeaderName, "subscription-key" } }; - appMetadata.Verify(a => a.GetApplicationMetadata()); - tokenGenerator.Verify(t => t.GenerateAccessToken("ttd", "test-app")); - userTokenProvider.Verify(u => u.GetUserToken()); - eFormidlingReceivers.Verify(er => er.GetEFormidlingReceivers(instance)); - eFormidlingMetadata.Verify(em => em.GenerateEFormidlingMetadata(instance)); + fixture.Mock().Verify(a => a.GetApplicationMetadata()); + fixture.Mock().Verify(t => t.GenerateAccessToken("ttd", "test-app")); + fixture.Mock().Verify(u => u.GetUserToken()); + fixture.Mock().Verify(er => er.GetEFormidlingReceivers(instance)); + fixture.Mock().Verify(em => em.GenerateEFormidlingMetadata(instance)); + var eFormidlingClient = fixture.Mock(); eFormidlingClient.Verify(ec => ec.CreateMessage(It.IsAny(), expectedReqHeaders)); eFormidlingClient.Verify(ec => ec.UploadAttachment( @@ -113,14 +152,16 @@ public void SendEFormidlingShipment() ) ); eFormidlingClient.Verify(ec => ec.SendMessage("41C1099C-7EDD-47F5-AD1F-6267B497796F", expectedReqHeaders)); - eventClient.Verify(e => e.AddEvent(EformidlingConstants.CheckInstanceStatusEventType, instance)); + fixture + .Mock() + .Verify(e => e.AddEvent(EformidlingConstants.CheckInstanceStatusEventType, instance)); eFormidlingClient.VerifyNoOtherCalls(); - eventClient.VerifyNoOtherCalls(); - tokenGenerator.VerifyNoOtherCalls(); - userTokenProvider.VerifyNoOtherCalls(); - eFormidlingReceivers.VerifyNoOtherCalls(); - appMetadata.VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); result.IsCompletedSuccessfully.Should().BeTrue(); } @@ -129,74 +170,17 @@ public void SendEFormidlingShipment() public void SendEFormidlingShipment_throws_exception_if_send_fails() { // Arrange - var logger = new NullLogger(); - var userTokenProvider = new Mock(); - var appMetadata = new Mock(); - var dataClient = new Mock(); - var eFormidlingReceivers = new Mock(); - var eventClient = new Mock(); - var appSettings = Options.Create( - new AppSettings { RuntimeCookieName = "AltinnStudioRuntime", EFormidlingSender = "980123456", } - ); - var platformSettings = Options.Create(new PlatformSettings { SubscriptionKey = "subscription-key", }); - var eFormidlingClient = new Mock(); - var tokenGenerator = new Mock(); - var eFormidlingMetadata = new Mock(); - var instance = new Instance + using var fixture = CreateFixture(eFormidlingClient => { - Id = "1337/41C1099C-7EDD-47F5-AD1F-6267B497796F", - InstanceOwner = new InstanceOwner { PartyId = "1337", }, - Data = new List() - }; - - appMetadata - .Setup(a => a.GetApplicationMetadata()) - .ReturnsAsync( - new ApplicationMetadata("ttd/test-app") - { - Org = "ttd", - EFormidling = new EFormidlingContract - { - Process = "urn:no:difi:profile:arkivmelding:plan:3.0", - Standard = "urn:no:difi:arkivmelding:xsd::arkivmelding", - TypeVersion = "v8", - Type = "arkivmelding", - SecurityLevel = 3, - DataTypes = new List() - } - } - ); - tokenGenerator.Setup(t => t.GenerateAccessToken("ttd", "test-app")).Returns("access-token"); - userTokenProvider.Setup(u => u.GetUserToken()).Returns("authz-token"); - eFormidlingReceivers.Setup(er => er.GetEFormidlingReceivers(instance)).ReturnsAsync(new List()); - eFormidlingMetadata - .Setup(em => em.GenerateEFormidlingMetadata(instance)) - .ReturnsAsync(() => - { - return ("fakefilename.txt", Stream.Null); - }); - eFormidlingClient - .Setup(ec => ec.SendMessage(It.IsAny(), It.IsAny>())) - .ThrowsAsync(new Exception("XUnit expected exception")); - - var defaultEformidlingService = new DefaultEFormidlingService( - logger, - userTokenProvider.Object, - appMetadata.Object, - dataClient.Object, - eFormidlingReceivers.Object, - eventClient.Object, - appSettings, - platformSettings, - eFormidlingClient.Object, - tokenGenerator.Object, - eFormidlingMetadata.Object - ); + eFormidlingClient + .Setup(ec => ec.SendMessage(It.IsAny(), It.IsAny>())) + .ThrowsAsync(new Exception("XUnit expected exception")); + }); + var (sp, instance, defaultEformidlingService) = fixture; // Act var result = defaultEformidlingService.SendEFormidlingShipment(instance); - // Assert // Assert var expectedReqHeaders = new Dictionary { @@ -205,11 +189,12 @@ public void SendEFormidlingShipment_throws_exception_if_send_fails() { General.SubscriptionKeyHeaderName, "subscription-key" } }; - appMetadata.Verify(a => a.GetApplicationMetadata()); - tokenGenerator.Verify(t => t.GenerateAccessToken("ttd", "test-app")); - userTokenProvider.Verify(u => u.GetUserToken()); - eFormidlingReceivers.Verify(er => er.GetEFormidlingReceivers(instance)); - eFormidlingMetadata.Verify(em => em.GenerateEFormidlingMetadata(instance)); + fixture.Mock().Verify(a => a.GetApplicationMetadata()); + fixture.Mock().Verify(t => t.GenerateAccessToken("ttd", "test-app")); + fixture.Mock().Verify(u => u.GetUserToken()); + fixture.Mock().Verify(er => er.GetEFormidlingReceivers(instance)); + fixture.Mock().Verify(em => em.GenerateEFormidlingMetadata(instance)); + var eFormidlingClient = fixture.Mock(); eFormidlingClient.Verify(ec => ec.CreateMessage(It.IsAny(), expectedReqHeaders)); eFormidlingClient.Verify(ec => ec.UploadAttachment( @@ -222,11 +207,11 @@ public void SendEFormidlingShipment_throws_exception_if_send_fails() eFormidlingClient.Verify(ec => ec.SendMessage("41C1099C-7EDD-47F5-AD1F-6267B497796F", expectedReqHeaders)); eFormidlingClient.VerifyNoOtherCalls(); - eventClient.VerifyNoOtherCalls(); - tokenGenerator.VerifyNoOtherCalls(); - userTokenProvider.VerifyNoOtherCalls(); - eFormidlingReceivers.VerifyNoOtherCalls(); - appMetadata.VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); + fixture.Mock().VerifyNoOtherCalls(); result.IsCompletedSuccessfully.Should().BeFalse(); }