using System.Text.Json; using Akka.Actor; using Akka.TestKit.Xunit2; using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection; using ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming; using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening; using ZB.MOM.WW.ScadaBridge.SiteRuntime; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Actors; /// /// Task 16: InstanceActor spawns NativeAlarmActor children from the flattened /// configuration and surfaces enriched native alarm state in the DebugView snapshot. /// public class InstanceActorNativeAlarmTests : TestKit, IDisposable { private readonly SiteStorageService _storage; private readonly ScriptCompilationService _compilationService; private readonly SharedScriptLibrary _sharedScriptLibrary; private readonly SiteRuntimeOptions _options = new(); private readonly string _dbFile; public InstanceActorNativeAlarmTests() { _dbFile = Path.Combine(Path.GetTempPath(), $"instance-native-{Guid.NewGuid():N}.db"); _storage = new SiteStorageService($"Data Source={_dbFile}", NullLogger.Instance); _storage.InitializeAsync().GetAwaiter().GetResult(); _compilationService = new ScriptCompilationService(NullLogger.Instance); _sharedScriptLibrary = new SharedScriptLibrary(_compilationService, NullLogger.Instance); } private IActorRef CreateInstanceActorWithDcl(string instanceName, FlattenedConfiguration config, IActorRef dclManager) => ActorOf(Props.Create(() => new InstanceActor( instanceName, JsonSerializer.Serialize(config), _storage, _compilationService, _sharedScriptLibrary, null, _options, NullLogger.Instance, dclManager))); private static FlattenedConfiguration ConfigWithNativeSource(string instanceName) => new() { InstanceUniqueName = instanceName, NativeAlarmSources = [ new ResolvedNativeAlarmSource { CanonicalName = "Pressure", ConnectionName = "Opc", SourceReference = "ns=2;s=T01" } ] }; [Fact] public void SpawnsNativeAlarmActor_WhichSubscribesViaDcl() { var dcl = CreateTestProbe(); CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst"), dcl.Ref); var req = dcl.ExpectMsg(); Assert.Equal("inst", req.InstanceUniqueName); Assert.Equal("Opc", req.ConnectionName); Assert.Equal("ns=2;s=T01", req.SourceReference); } [Fact] public void DebugViewSnapshot_IncludesNativeAlarm_AfterTransition() { var dcl = CreateTestProbe(); var actor = CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst"), dcl.Ref); // Simulate the NativeAlarmActor emitting an enriched event upward. actor.Tell(new AlarmStateChanged("inst", "T01.Hi", AlarmState.Active, 800, DateTimeOffset.UtcNow) { Kind = AlarmKind.NativeOpcUa, SourceReference = "T01.Hi", Condition = new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 800) }); actor.Tell(new SubscribeDebugViewRequest("inst", "c")); var snap = ExpectMsg(); Assert.Contains(snap.AlarmStates, a => a.SourceReference == "T01.Hi" && a.Kind == AlarmKind.NativeOpcUa && a.Condition.Severity == 800); } void IDisposable.Dispose() { Shutdown(); try { File.Delete(_dbFile); } catch { /* cleanup */ } } }