using Akka.Actor; using Akka.TestKit.Xunit2; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using ScadaLink.AuditLog; using ScadaLink.AuditLog.Central; using ScadaLink.AuditLog.Payload; namespace ScadaLink.AuditLog.Tests.Central; /// /// Bundle E (M6-T9) coverage for the central-side payload-filter redactor /// failure bridge. M5 wired the SITE bridge /// (HealthMetricsAuditRedactionFailureCounter) that pushes increments /// into the site health report; M6 mirrors that with /// so the same payload /// filter — when it runs on the central writer paths — surfaces failures on /// the central . /// public class CentralAuditRedactionFailureCounterTests : TestKit { [Fact] public void Increment_Routes_To_Snapshot() { var snapshot = new AuditCentralHealthSnapshot(); var counter = new CentralAuditRedactionFailureCounter(snapshot); counter.Increment(); counter.Increment(); counter.Increment(); Assert.Equal(3, snapshot.AuditRedactionFailure); } [Fact] public void Construction_With_Null_Snapshot_Throws() { Assert.Throws( () => new CentralAuditRedactionFailureCounter(null!)); } [Fact] public void AddAuditLogCentralMaintenance_Replaces_IAuditRedactionFailureCounter_With_CentralImpl() { // AddAuditLog registers NoOp; AddAuditLogCentralMaintenance is the // override path. The replaced binding MUST resolve to the central // bridge — a site host that wires AddAuditLogHealthMetricsBridge // instead would resolve to the site bridge (covered in // AddAuditLogTests). var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["AuditLog:SiteWriter:DatabasePath"] = ":memory:", }) .Build(); var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>)); // AuditCentralHealthSnapshot no longer takes a tracker dependency — // the tracker is constructed later by the Akka bootstrap because its // ctor needs an ActorSystem (not a DI-resolvable singleton). The // snapshot itself composes purely from primitives. services.AddAuditLog(config); services.AddAuditLogCentralMaintenance(config); using var provider = services.BuildServiceProvider(); var counter = provider.GetRequiredService(); Assert.IsType(counter); } [Fact] public void AddAuditLog_Default_IAuditRedactionFailureCounter_Is_NoOp() { // Sanity check: without AddAuditLogCentralMaintenance the default // remains the NoOp from M5 — the central bridge only takes effect // when the central-only registration runs. var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["AuditLog:SiteWriter:DatabasePath"] = ":memory:", }) .Build(); var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>)); services.AddAuditLog(config); using var provider = services.BuildServiceProvider(); var counter = provider.GetRequiredService(); Assert.IsType(counter); } }