Wire the Notification Outbox into the Host central role: - Program.cs: call AddNotificationOutbox() on the central path (binds NotificationOutboxOptions via BindConfiguration; no explicit Configure). - AkkaHostedService.RegisterCentralActors(): create the NotificationOutboxActor as a non-role-scoped central cluster singleton + proxy, then send RegisterNotificationOutbox(proxy) to the CentralCommunicationActor. - appsettings.Central.json: add the ScadaLink:NotificationOutbox section with the NotificationOutboxOptions defaults. - SiteServiceRegistration: remove the now-dead AddNotificationService() call - sites forward notifications to central rather than delivering over SMTP, and no site component consumes the SMTP machinery. - Host.csproj: add the ScadaLink.NotificationOutbox project reference. - Tests: add central outbox singleton/proxy actor-path assertions, drop the site OAuth2TokenService/INotificationDeliveryService resolution assertions, and add NotificationOutbox to the component-library IConfiguration check.
67 lines
3.2 KiB
C#
67 lines
3.2 KiB
C#
using System.Reflection;
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
namespace ScadaLink.Host.Tests;
|
|
|
|
public class OptionsTests
|
|
{
|
|
/// <summary>
|
|
/// Verify no component library (excluding Host) uses IConfiguration or accepts it
|
|
/// in its AddXxx() extension method. Component libraries should only depend on
|
|
/// DI abstractions and Options pattern, not on Configuration directly.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ComponentLibraries_DoNotAcceptIConfigurationInAddMethods()
|
|
{
|
|
// All component assemblies (excluding Host itself and Commons)
|
|
var componentAssemblies = new[]
|
|
{
|
|
typeof(ClusterInfrastructure.ServiceCollectionExtensions).Assembly,
|
|
typeof(Communication.ServiceCollectionExtensions).Assembly,
|
|
typeof(HealthMonitoring.ServiceCollectionExtensions).Assembly,
|
|
typeof(ExternalSystemGateway.ServiceCollectionExtensions).Assembly,
|
|
typeof(NotificationService.ServiceCollectionExtensions).Assembly,
|
|
typeof(NotificationOutbox.ServiceCollectionExtensions).Assembly,
|
|
typeof(TemplateEngine.ServiceCollectionExtensions).Assembly,
|
|
typeof(DeploymentManager.ServiceCollectionExtensions).Assembly,
|
|
typeof(Security.ServiceCollectionExtensions).Assembly,
|
|
typeof(ConfigurationDatabase.ServiceCollectionExtensions).Assembly,
|
|
typeof(SiteRuntime.ServiceCollectionExtensions).Assembly,
|
|
typeof(DataConnectionLayer.ServiceCollectionExtensions).Assembly,
|
|
typeof(StoreAndForward.ServiceCollectionExtensions).Assembly,
|
|
typeof(SiteEventLogging.ServiceCollectionExtensions).Assembly,
|
|
typeof(CentralUI.ServiceCollectionExtensions).Assembly,
|
|
typeof(InboundAPI.ServiceCollectionExtensions).Assembly,
|
|
};
|
|
|
|
foreach (var assembly in componentAssemblies)
|
|
{
|
|
// Check that the assembly does not reference Microsoft.Extensions.Configuration
|
|
var configRef = assembly.GetReferencedAssemblies()
|
|
.FirstOrDefault(a => a.Name == "Microsoft.Extensions.Configuration.Abstractions");
|
|
|
|
// Find all public static extension methods named Add*
|
|
var extensionClasses = assembly.GetExportedTypes()
|
|
.Where(t => t.IsClass && t.IsAbstract && t.IsSealed); // static classes
|
|
|
|
foreach (var cls in extensionClasses)
|
|
{
|
|
var addMethods = cls.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
|
.Where(m => m.Name.StartsWith("Add") || m.Name.StartsWith("Map"));
|
|
|
|
foreach (var method in addMethods)
|
|
{
|
|
var parameters = method.GetParameters();
|
|
foreach (var param in parameters)
|
|
{
|
|
Assert.False(
|
|
typeof(IConfiguration).IsAssignableFrom(param.ParameterType),
|
|
$"{assembly.GetName().Name}: {cls.Name}.{method.Name} accepts IConfiguration parameter '{param.Name}'. " +
|
|
"Component libraries should use the Options pattern, not IConfiguration.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|