using Akka.Actor; using Akka.TestKit.Xunit2; using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Streaming; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Streaming; /// /// WP-23: Site-Wide Akka Stream tests. /// WP-25: Debug View Backend tests (subscribe/unsubscribe). /// public class SiteStreamManagerTests : TestKit, IDisposable { private readonly SiteStreamManager _streamManager; public SiteStreamManagerTests() { var options = new SiteRuntimeOptions { StreamBufferSize = 100 }; _streamManager = new SiteStreamManager( options, NullLogger.Instance); _streamManager.Initialize(Sys); } void IDisposable.Dispose() { Shutdown(); } [Fact] public void Subscribe_CreatesSubscription() { var probe = CreateTestProbe(); var id = _streamManager.Subscribe("Pump1", probe.Ref); Assert.NotNull(id); Assert.Equal(1, _streamManager.SubscriptionCount); } [Fact] public void Unsubscribe_RemovesSubscription() { var probe = CreateTestProbe(); var id = _streamManager.Subscribe("Pump1", probe.Ref); Assert.True(_streamManager.Unsubscribe(id)); Assert.Equal(0, _streamManager.SubscriptionCount); } [Fact] public void Unsubscribe_InvalidId_ReturnsFalse() { Assert.False(_streamManager.Unsubscribe("nonexistent")); } [Fact] public void PublishAttributeValueChanged_ForwardsToSubscriber() { var probe = CreateTestProbe(); _streamManager.Subscribe("Pump1", probe.Ref); var changed = new AttributeValueChanged( "Pump1", "Temperature", "Temperature", "100", "Good", DateTimeOffset.UtcNow); _streamManager.PublishAttributeValueChanged(changed); var received = probe.ExpectMsg(TimeSpan.FromSeconds(3)); Assert.Equal("Pump1", received.InstanceUniqueName); Assert.Equal("Temperature", received.AttributeName); } [Fact] public void PublishAlarmStateChanged_ForwardsToSubscriber() { var probe = CreateTestProbe(); _streamManager.Subscribe("Pump1", probe.Ref); var changed = new AlarmStateChanged( "Pump1", "HighTemp", AlarmState.Active, 1, DateTimeOffset.UtcNow); _streamManager.PublishAlarmStateChanged(changed); var received = probe.ExpectMsg(TimeSpan.FromSeconds(3)); Assert.Equal("Pump1", received.InstanceUniqueName); Assert.Equal(AlarmState.Active, received.State); } [Fact] public void PublishAttributeValueChanged_FiltersbyInstance() { var probe1 = CreateTestProbe(); var probe2 = CreateTestProbe(); _streamManager.Subscribe("Pump1", probe1.Ref); _streamManager.Subscribe("Pump2", probe2.Ref); var changed = new AttributeValueChanged( "Pump1", "Temperature", "Temperature", "100", "Good", DateTimeOffset.UtcNow); _streamManager.PublishAttributeValueChanged(changed); // Pump1 subscriber should receive probe1.ExpectMsg(TimeSpan.FromSeconds(3)); // Pump2 subscriber should NOT receive probe2.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); } [Fact] public void SubscribeSiteAlarms_ForwardsAlarmsForAllInstances_ButNotAttributes() { var probe = CreateTestProbe(); _streamManager.SubscribeSiteAlarms(probe.Ref); // Alarm events from two different instances — both should arrive. _streamManager.PublishAlarmStateChanged(new AlarmStateChanged( "Pump1", "HighTemp", AlarmState.Active, 1, DateTimeOffset.UtcNow)); _streamManager.PublishAlarmStateChanged(new AlarmStateChanged( "Pump2", "LowFlow", AlarmState.Active, 2, DateTimeOffset.UtcNow)); // Attribute events must be filtered out entirely. _streamManager.PublishAttributeValueChanged(new AttributeValueChanged( "Pump1", "Temperature", "Temperature", "100", "Good", DateTimeOffset.UtcNow)); _streamManager.PublishAttributeValueChanged(new AttributeValueChanged( "Pump3", "Flow", "Flow", "42", "Good", DateTimeOffset.UtcNow)); // Collect the two alarms (order across instances is not guaranteed). var received = new[] { probe.ExpectMsg(TimeSpan.FromSeconds(3)), probe.ExpectMsg(TimeSpan.FromSeconds(3)), }; var instances = received.Select(a => a.InstanceUniqueName).OrderBy(n => n).ToArray(); Assert.Equal(new[] { "Pump1", "Pump2" }, instances); // No attribute events (nor any further alarm) should be delivered. probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); } [Fact] public void SubscribeSiteAlarms_SubscriptionRemovableViaUnsubscribe() { var probe = CreateTestProbe(); var id = _streamManager.SubscribeSiteAlarms(probe.Ref); Assert.NotNull(id); Assert.Equal(1, _streamManager.SubscriptionCount); Assert.True(_streamManager.Unsubscribe(id)); Assert.Equal(0, _streamManager.SubscriptionCount); } [Fact] public void RemoveSubscriber_RemovesAllSubscriptionsForActor() { var probe = CreateTestProbe(); _streamManager.Subscribe("Pump1", probe.Ref); _streamManager.Subscribe("Pump2", probe.Ref); Assert.Equal(2, _streamManager.SubscriptionCount); _streamManager.RemoveSubscriber(probe.Ref); Assert.Equal(0, _streamManager.SubscriptionCount); } }