using Google.Protobuf.WellKnownTypes; using ScadaLink.Communication.Grpc; using ScadaLink.Commons.Messages.Streaming; using ScadaLink.Commons.Types.Enums; namespace ScadaLink.Communication.Tests.Grpc; public class SiteStreamGrpcClientTests { [Fact] public void ConvertToDomainEvent_AttributeChanged_MapsCorrectly() { var ts = DateTimeOffset.UtcNow; var evt = new SiteStreamEvent { CorrelationId = "corr-1", AttributeChanged = new AttributeValueUpdate { InstanceUniqueName = "Site1.Pump01", AttributePath = "Modules.IO", AttributeName = "Temperature", Value = "42.5", Quality = Quality.Good, Timestamp = Timestamp.FromDateTimeOffset(ts) } }; var result = SiteStreamGrpcClient.ConvertToDomainEvent(evt); var attr = Assert.IsType(result); Assert.Equal("Site1.Pump01", attr.InstanceUniqueName); Assert.Equal("Modules.IO", attr.AttributePath); Assert.Equal("Temperature", attr.AttributeName); Assert.Equal("42.5", attr.Value); Assert.Equal("Good", attr.Quality); Assert.Equal(ts, attr.Timestamp); } [Fact] public void ConvertToDomainEvent_AlarmChanged_MapsCorrectly() { var ts = DateTimeOffset.UtcNow; var evt = new SiteStreamEvent { CorrelationId = "corr-2", AlarmChanged = new AlarmStateUpdate { InstanceUniqueName = "Site1.Motor01", AlarmName = "OverTemp", State = AlarmStateEnum.AlarmStateActive, Priority = 3, Timestamp = Timestamp.FromDateTimeOffset(ts) } }; var result = SiteStreamGrpcClient.ConvertToDomainEvent(evt); var alarm = Assert.IsType(result); Assert.Equal("Site1.Motor01", alarm.InstanceUniqueName); Assert.Equal("OverTemp", alarm.AlarmName); Assert.Equal(AlarmState.Active, alarm.State); Assert.Equal(3, alarm.Priority); Assert.Equal(ts, alarm.Timestamp); } [Fact] public void ConvertToDomainEvent_UnknownEvent_ReturnsNull() { var evt = new SiteStreamEvent { CorrelationId = "corr-3" // No oneof case set }; var result = SiteStreamGrpcClient.ConvertToDomainEvent(evt); Assert.Null(result); } [Theory] [InlineData(Quality.Good, "Good")] [InlineData(Quality.Uncertain, "Uncertain")] [InlineData(Quality.Bad, "Bad")] [InlineData(Quality.Unspecified, "Unknown")] public void MapQuality_AllValues(Quality input, string expected) { var result = SiteStreamGrpcClient.MapQuality(input); Assert.Equal(expected, result); } [Theory] [InlineData(AlarmStateEnum.AlarmStateNormal, AlarmState.Normal)] [InlineData(AlarmStateEnum.AlarmStateActive, AlarmState.Active)] [InlineData(AlarmStateEnum.AlarmStateUnspecified, AlarmState.Normal)] public void MapAlarmState_AllValues(AlarmStateEnum input, AlarmState expected) { var result = SiteStreamGrpcClient.MapAlarmState(input); Assert.Equal(expected, result); } [Fact] public void Unsubscribe_CancelsSubscription() { // We can't easily test the full Subscribe flow without a real gRPC server, // but we can test the Unsubscribe path by registering a CTS directly. // Use the internal AddSubscription helper for testability. var client = SiteStreamGrpcClient.CreateForTesting(); var cts = new CancellationTokenSource(); client.AddSubscriptionForTesting("corr-test", cts); Assert.False(cts.IsCancellationRequested); client.Unsubscribe("corr-test"); Assert.True(cts.IsCancellationRequested); } [Fact] public void Unsubscribe_NonExistent_DoesNotThrow() { var client = SiteStreamGrpcClient.CreateForTesting(); client.Unsubscribe("does-not-exist"); // Should not throw } [Fact] public async Task DisposeAsync_CancelsAllSubscriptions() { var client = SiteStreamGrpcClient.CreateForTesting(); var cts1 = new CancellationTokenSource(); var cts2 = new CancellationTokenSource(); client.AddSubscriptionForTesting("corr-1", cts1); client.AddSubscriptionForTesting("corr-2", cts2); await client.DisposeAsync(); Assert.True(cts1.IsCancellationRequested); Assert.True(cts2.IsCancellationRequested); } }