using Google.Protobuf; using Google.Protobuf.WellKnownTypes; using ScadaLink.Communication.Grpc; namespace ScadaLink.Communication.Tests.Grpc; public class ProtoRoundtripTests { [Theory] [InlineData(Quality.Good)] [InlineData(Quality.Uncertain)] [InlineData(Quality.Bad)] [InlineData(Quality.Unspecified)] public void AttributeValueUpdate_RoundTrip(Quality quality) { var timestamp = Timestamp.FromDateTimeOffset( new DateTimeOffset(2026, 3, 21, 12, 0, 0, TimeSpan.Zero)); var original = new AttributeValueUpdate { InstanceUniqueName = "Site1.Pump01", AttributePath = "Modules.PressureModule", AttributeName = "CurrentPressure", Value = "42.5", Quality = quality, Timestamp = timestamp }; var bytes = original.ToByteArray(); var deserialized = AttributeValueUpdate.Parser.ParseFrom(bytes); Assert.Equal(original.InstanceUniqueName, deserialized.InstanceUniqueName); Assert.Equal(original.AttributePath, deserialized.AttributePath); Assert.Equal(original.AttributeName, deserialized.AttributeName); Assert.Equal(original.Value, deserialized.Value); Assert.Equal(original.Quality, deserialized.Quality); Assert.Equal(original.Timestamp, deserialized.Timestamp); Assert.Equal(timestamp.Seconds, deserialized.Timestamp.Seconds); Assert.Equal(timestamp.Nanos, deserialized.Timestamp.Nanos); } [Theory] [InlineData(AlarmStateEnum.AlarmStateNormal)] [InlineData(AlarmStateEnum.AlarmStateActive)] [InlineData(AlarmStateEnum.AlarmStateUnspecified)] public void AlarmStateUpdate_RoundTrip(AlarmStateEnum state) { var timestamp = Timestamp.FromDateTimeOffset( new DateTimeOffset(2026, 3, 21, 12, 30, 0, TimeSpan.Zero)); var original = new AlarmStateUpdate { InstanceUniqueName = "Site1.Pump01", AlarmName = "HighPressure", State = state, Priority = 3, Timestamp = timestamp }; var bytes = original.ToByteArray(); var deserialized = AlarmStateUpdate.Parser.ParseFrom(bytes); Assert.Equal(original.InstanceUniqueName, deserialized.InstanceUniqueName); Assert.Equal(original.AlarmName, deserialized.AlarmName); Assert.Equal(original.State, deserialized.State); Assert.Equal(original.Priority, deserialized.Priority); Assert.Equal(original.Timestamp, deserialized.Timestamp); } [Fact] public void SiteStreamEvent_OneOf_AttributeChanged() { var evt = new SiteStreamEvent { CorrelationId = "corr-123", AttributeChanged = new AttributeValueUpdate { InstanceUniqueName = "Site1.Pump01", AttributePath = "Modules.PressureModule", AttributeName = "CurrentPressure", Value = "42.5", Quality = Quality.Good, Timestamp = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow) } }; Assert.Equal(SiteStreamEvent.EventOneofCase.AttributeChanged, evt.EventCase); Assert.NotNull(evt.AttributeChanged); Assert.Null(evt.AlarmChanged); // Round-trip var bytes = evt.ToByteArray(); var deserialized = SiteStreamEvent.Parser.ParseFrom(bytes); Assert.Equal(SiteStreamEvent.EventOneofCase.AttributeChanged, deserialized.EventCase); Assert.Equal("corr-123", deserialized.CorrelationId); Assert.Equal("42.5", deserialized.AttributeChanged.Value); } [Fact] public void SiteStreamEvent_OneOf_AlarmChanged() { var evt = new SiteStreamEvent { CorrelationId = "corr-456", AlarmChanged = new AlarmStateUpdate { InstanceUniqueName = "Site1.Pump01", AlarmName = "HighPressure", State = AlarmStateEnum.AlarmStateActive, Priority = 1, Timestamp = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow) } }; Assert.Equal(SiteStreamEvent.EventOneofCase.AlarmChanged, evt.EventCase); Assert.NotNull(evt.AlarmChanged); Assert.Null(evt.AttributeChanged); // Round-trip var bytes = evt.ToByteArray(); var deserialized = SiteStreamEvent.Parser.ParseFrom(bytes); Assert.Equal(SiteStreamEvent.EventOneofCase.AlarmChanged, deserialized.EventCase); Assert.Equal("corr-456", deserialized.CorrelationId); Assert.Equal(AlarmStateEnum.AlarmStateActive, deserialized.AlarmChanged.State); Assert.Equal(1, deserialized.AlarmChanged.Priority); } [Fact] public void Timestamp_DateTimeOffset_FullRoundTrip() { var original = new DateTimeOffset(2026, 3, 21, 14, 30, 45, 123, TimeSpan.Zero); var update = new AttributeValueUpdate { InstanceUniqueName = "Motor-1", AttributePath = "Speed", AttributeName = "Speed", Value = "42.5", Quality = Quality.Good, Timestamp = Timestamp.FromDateTimeOffset(original) }; var bytes = update.ToByteArray(); var deserialized = AttributeValueUpdate.Parser.ParseFrom(bytes); var roundTripped = deserialized.Timestamp.ToDateTimeOffset(); Assert.Equal(original.Year, roundTripped.Year); Assert.Equal(original.Month, roundTripped.Month); Assert.Equal(original.Day, roundTripped.Day); Assert.Equal(original.Hour, roundTripped.Hour); Assert.Equal(original.Minute, roundTripped.Minute); Assert.Equal(original.Second, roundTripped.Second); Assert.Equal(original.Millisecond, roundTripped.Millisecond); Assert.Equal(TimeSpan.Zero, roundTripped.Offset); } }