using System.Linq; using Akka.Actor; using Akka.Event; using Akka.TestKit.Xunit2; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using ZB.MOM.WW.ScadaBridge.Host.Actors; namespace ZB.MOM.WW.ScadaBridge.Host.Tests; /// /// WP-15: Tests for DeadLetterMonitorActor. /// public class DeadLetterMonitorTests : TestKit { private readonly ILogger _logger = NullLoggerFactory.Instance.CreateLogger(); [Fact] public void DeadLetterMonitor_StartsWithZeroCount() { var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(_logger))); monitor.Tell(GetDeadLetterCount.Instance); var response = ExpectMsg(); Assert.Equal(0, response.Count); } [Fact] public void DeadLetterMonitor_IncrementsOnDeadLetter() { var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(_logger))); // Ensure actor has started and subscribed by sending a message and waiting for response monitor.Tell(GetDeadLetterCount.Instance); ExpectMsg(); // Now publish dead letters — actor is guaranteed to be subscribed Sys.EventStream.Publish(new DeadLetter("test-message-1", Sys.DeadLetters, Sys.DeadLetters)); Sys.EventStream.Publish(new DeadLetter("test-message-2", Sys.DeadLetters, Sys.DeadLetters)); // Use AwaitAssert to handle async event delivery AwaitAssert(() => { monitor.Tell(GetDeadLetterCount.Instance); var response = ExpectMsg(); Assert.Equal(2, response.Count); }); } [Fact] public async Task DeadLetterStorm_LogsAtMostWindowLimit_ButCountsAll() { // Review 01 [Low]: a failover/undeploy storm produced one Warning per // dead letter — unbounded log flood. Health metric counting is untouched. var logger = Substitute.For>(); var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(logger, null))); // Ensure the actor has started before the storm. monitor.Tell(GetDeadLetterCount.Instance); ExpectMsg(); for (var i = 0; i < 50; i++) monitor.Tell(new DeadLetter($"m{i}", TestActor, TestActor)); var count = await monitor.Ask(GetDeadLetterCount.Instance); Assert.Equal(50, count.Count); // every dead letter still counted var warnings = logger.ReceivedCalls().Count(c => c.GetMethodInfo().Name == "Log" && (Microsoft.Extensions.Logging.LogLevel)c.GetArguments()[0]! == Microsoft.Extensions.Logging.LogLevel.Warning); // The per-letter cap plus the one "limit reached" notice. Assert.True(warnings <= DeadLetterMonitorActor.MaxWarningsPerWindow + 1, $"expected <= {DeadLetterMonitorActor.MaxWarningsPerWindow + 1} warnings, got {warnings}"); } [Fact] public void DeadLetterMonitor_CountAccumulates() { var monitor = Sys.ActorOf(Props.Create(() => new DeadLetterMonitorActor(_logger))); // Ensure actor is started and subscribed monitor.Tell(GetDeadLetterCount.Instance); ExpectMsg(); // Send 5 dead letters for (var i = 0; i < 5; i++) { Sys.EventStream.Publish( new DeadLetter($"message-{i}", Sys.DeadLetters, Sys.DeadLetters)); } AwaitAssert(() => { monitor.Tell(GetDeadLetterCount.Instance); var response = ExpectMsg(); Assert.Equal(5, response.Count); }); } }