Skip to content

Commit

Permalink
AppImplementatinFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
martinothamar committed Sep 17, 2024
1 parent 5c250e6 commit 246ef16
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 107 deletions.
46 changes: 46 additions & 0 deletions src/Altinn.App.Core/AppImplementationFactory.cs
Original file line number Diff line number Diff line change
@@ -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<AppImplementationFactory>();
return services;
}

public static T GetRequiredAppImplementation<T>(this IServiceProvider sp)
where T : class => sp.GetRequiredService<AppImplementationFactory>().GetRequiredImplementation<T>();

public static T? GetAppImplementation<T>(this IServiceProvider sp)
where T : class => sp.GetRequiredService<AppImplementationFactory>().GetImplementation<T>();

public static IEnumerable<T> GetAppImplementations<T>(this IServiceProvider sp)
where T : class => sp.GetRequiredService<AppImplementationFactory>().GetImplementations<T>();
}

file sealed class AppImplementationFactory
{
private readonly IServiceProvider _sp;

public AppImplementationFactory(IServiceProvider serviceProvider)
{
_sp = serviceProvider;
}

public T GetRequiredImplementation<T>()
where T : class => _sp.GetRequiredService<T>();

public T? GetImplementation<T>()
where T : class => _sp.GetService<T>();

public IEnumerable<T> GetImplementations<T>()
where T : class => _sp.GetServices<T>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -42,13 +43,12 @@ public DefaultEFormidlingService(
IUserTokenProvider userTokenProvider,
IAppMetadata appMetadata,
IDataClient dataClient,
IEFormidlingReceivers eFormidlingReceivers,
IEventsClient eventClient,
IServiceProvider sp,
IOptions<AppSettings>? appSettings = null,
IOptions<PlatformSettings>? platformSettings = null,
IEFormidlingClient? eFormidlingClient = null,
IAccessTokenGenerator? tokenGenerator = null,
IEFormidlingMetadata? eFormidlingMetadata = null
IAccessTokenGenerator? tokenGenerator = null
)
{
_logger = logger;
Expand All @@ -57,10 +57,10 @@ public DefaultEFormidlingService(
_platformSettings = platformSettings?.Value;
_userTokenProvider = userTokenProvider;
_eFormidlingClient = eFormidlingClient;
_eFormidlingMetadata = eFormidlingMetadata;
_eFormidlingMetadata = sp.GetAppImplementation<IEFormidlingMetadata>();
_eFormidlingReceivers = sp.GetRequiredAppImplementation<IEFormidlingReceivers>();
_appMetadata = appMetadata;
_dataClient = dataClient;
_eFormidlingReceivers = eFormidlingReceivers;
_eventClient = eventClient;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Altinn.App.Core/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ public static void AddAppServices(
IWebHostEnvironment env
)
{
services.AddAppImplementationFactory();

// Services for Altinn App
services.TryAddTransient<IPDP, PDPAppSI>();
AddValidationServices(services, configuration);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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<DefaultEFormidlingService>();
public Mock<T> Mock<T>()
where T : class => Moq.Mock.Get(ServiceProvider.GetRequiredService<T>());

public void Dispose()
{
if (ServiceProvider is IDisposable disposable)
{
disposable.Dispose();
}
}
}

private Fixture CreateFixture(Action<Mock<IEFormidlingClient>>? setupEFormidlingClient = null)
{
var services = new ServiceCollection();
services.AddAppImplementationFactory();
services.AddLogging(logging =>
{
logging.ClearProviders();
logging.AddProvider(NullLoggerProvider.Instance);
});

var userTokenProvider = new Mock<IUserTokenProvider>();
var appMetadata = new Mock<IAppMetadata>();
var dataClient = new Mock<IDataClient>();
Expand Down Expand Up @@ -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<IEFormidlingService, DefaultEFormidlingService>();

var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetRequiredService<IEFormidlingService>();
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);
Expand All @@ -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<IAppMetadata>().Verify(a => a.GetApplicationMetadata());
fixture.Mock<IAccessTokenGenerator>().Verify(t => t.GenerateAccessToken("ttd", "test-app"));
fixture.Mock<IUserTokenProvider>().Verify(u => u.GetUserToken());
fixture.Mock<IEFormidlingReceivers>().Verify(er => er.GetEFormidlingReceivers(instance));
fixture.Mock<IEFormidlingMetadata>().Verify(em => em.GenerateEFormidlingMetadata(instance));
var eFormidlingClient = fixture.Mock<IEFormidlingClient>();
eFormidlingClient.Verify(ec => ec.CreateMessage(It.IsAny<StandardBusinessDocument>(), expectedReqHeaders));
eFormidlingClient.Verify(ec =>
ec.UploadAttachment(
Expand All @@ -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<IEventsClient>()
.Verify(e => e.AddEvent(EformidlingConstants.CheckInstanceStatusEventType, instance));

eFormidlingClient.VerifyNoOtherCalls();
eventClient.VerifyNoOtherCalls();
tokenGenerator.VerifyNoOtherCalls();
userTokenProvider.VerifyNoOtherCalls();
eFormidlingReceivers.VerifyNoOtherCalls();
appMetadata.VerifyNoOtherCalls();
fixture.Mock<IEventsClient>().VerifyNoOtherCalls();
fixture.Mock<IAccessTokenGenerator>().VerifyNoOtherCalls();
fixture.Mock<IUserTokenProvider>().VerifyNoOtherCalls();
fixture.Mock<IEFormidlingReceivers>().VerifyNoOtherCalls();
fixture.Mock<IAppMetadata>().VerifyNoOtherCalls();

result.IsCompletedSuccessfully.Should().BeTrue();
}
Expand All @@ -129,74 +170,17 @@ public void SendEFormidlingShipment()
public void SendEFormidlingShipment_throws_exception_if_send_fails()
{
// Arrange
var logger = new NullLogger<DefaultEFormidlingService>();
var userTokenProvider = new Mock<IUserTokenProvider>();
var appMetadata = new Mock<IAppMetadata>();
var dataClient = new Mock<IDataClient>();
var eFormidlingReceivers = new Mock<IEFormidlingReceivers>();
var eventClient = new Mock<IEventsClient>();
var appSettings = Options.Create(
new AppSettings { RuntimeCookieName = "AltinnStudioRuntime", EFormidlingSender = "980123456", }
);
var platformSettings = Options.Create(new PlatformSettings { SubscriptionKey = "subscription-key", });
var eFormidlingClient = new Mock<IEFormidlingClient>();
var tokenGenerator = new Mock<IAccessTokenGenerator>();
var eFormidlingMetadata = new Mock<IEFormidlingMetadata>();
var instance = new Instance
using var fixture = CreateFixture(eFormidlingClient =>
{
Id = "1337/41C1099C-7EDD-47F5-AD1F-6267B497796F",
InstanceOwner = new InstanceOwner { PartyId = "1337", },
Data = new List<DataElement>()
};

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<string>()
}
}
);
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<Receiver>());
eFormidlingMetadata
.Setup(em => em.GenerateEFormidlingMetadata(instance))
.ReturnsAsync(() =>
{
return ("fakefilename.txt", Stream.Null);
});
eFormidlingClient
.Setup(ec => ec.SendMessage(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.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<string>(), It.IsAny<Dictionary<string, string>>()))
.ThrowsAsync(new Exception("XUnit expected exception"));
});
var (sp, instance, defaultEformidlingService) = fixture;

// Act
var result = defaultEformidlingService.SendEFormidlingShipment(instance);

// Assert
// Assert
var expectedReqHeaders = new Dictionary<string, string>
{
Expand All @@ -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<IAppMetadata>().Verify(a => a.GetApplicationMetadata());
fixture.Mock<IAccessTokenGenerator>().Verify(t => t.GenerateAccessToken("ttd", "test-app"));
fixture.Mock<IUserTokenProvider>().Verify(u => u.GetUserToken());
fixture.Mock<IEFormidlingReceivers>().Verify(er => er.GetEFormidlingReceivers(instance));
fixture.Mock<IEFormidlingMetadata>().Verify(em => em.GenerateEFormidlingMetadata(instance));
var eFormidlingClient = fixture.Mock<IEFormidlingClient>();
eFormidlingClient.Verify(ec => ec.CreateMessage(It.IsAny<StandardBusinessDocument>(), expectedReqHeaders));
eFormidlingClient.Verify(ec =>
ec.UploadAttachment(
Expand All @@ -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<IEventsClient>().VerifyNoOtherCalls();
fixture.Mock<IAccessTokenGenerator>().VerifyNoOtherCalls();
fixture.Mock<IUserTokenProvider>().VerifyNoOtherCalls();
fixture.Mock<IEFormidlingReceivers>().VerifyNoOtherCalls();
fixture.Mock<IAppMetadata>().VerifyNoOtherCalls();

result.IsCompletedSuccessfully.Should().BeFalse();
}
Expand Down

0 comments on commit 246ef16

Please sign in to comment.