using Akka.TestKit.Xunit2; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; namespace ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests; /// /// Characterization pin for the intra-cluster S&F replication contract. A /// (carrying a full ) /// is Told from the active node to the standby node's replication actor and rides /// the Akka default reflective-JSON wire format. Nothing tested that it round-trips /// or that its type identity is stable — a rename/move, a dropped setter, or a /// non-default-constructible message would silently break standby buffer sync and /// only surface as a divergent buffer after a failover. /// /// The serializer swap (proto / explicit bindings) is deferred; until then this pin /// is the guardrail. /// public class ReplicationWireSerializationPinTests : TestKit { private T RoundTrip(T message) { var serialization = Sys.Serialization; var serializer = serialization.FindSerializerFor(message); var bytes = serializer.ToBinary(message); return (T)serialization.Deserialize(bytes, serializer.Identifier, message!.GetType()); } [Fact] public void ReplicationOperation_WithFullMessage_RoundTripsOnTheWire() { var message = new StoreAndForwardMessage { Id = Guid.NewGuid().ToString("N"), Category = StoreAndForwardCategory.Notification, Target = "Operators", PayloadJson = "{\"notificationId\":\"abc\"}", RetryCount = 4, MaxRetries = 0, RetryIntervalMs = 30000, CreatedAt = DateTimeOffset.UtcNow, LastAttemptAt = DateTimeOffset.UtcNow, Status = StoreAndForwardMessageStatus.Parked, LastError = "central rejected", OriginInstanceName = "Plant.Pump3", ExecutionId = Guid.NewGuid(), SourceScript = "ScriptActor:MonitorSpeed", ParentExecutionId = Guid.NewGuid(), }; var original = new ReplicationOperation(ReplicationOperationType.Park, message.Id, message); var back = RoundTrip(original); Assert.Equal(original.OperationType, back.OperationType); Assert.Equal(original.MessageId, back.MessageId); Assert.NotNull(back.Message); var m = back.Message!; Assert.Equal(message.Id, m.Id); Assert.Equal(message.Category, m.Category); Assert.Equal(message.Target, m.Target); Assert.Equal(message.PayloadJson, m.PayloadJson); Assert.Equal(message.RetryCount, m.RetryCount); Assert.Equal(message.MaxRetries, m.MaxRetries); Assert.Equal(message.RetryIntervalMs, m.RetryIntervalMs); Assert.Equal(message.CreatedAt, m.CreatedAt); Assert.Equal(message.LastAttemptAt, m.LastAttemptAt); Assert.Equal(message.Status, m.Status); Assert.Equal(message.LastError, m.LastError); Assert.Equal(message.OriginInstanceName, m.OriginInstanceName); Assert.Equal(message.ExecutionId, m.ExecutionId); Assert.Equal(message.SourceScript, m.SourceScript); Assert.Equal(message.ParentExecutionId, m.ParentExecutionId); } // Type-identity pins: the reflective-JSON wire embeds CLR type manifests, so a // rename/move of either type silently breaks standby replication across a // rolling upgrade. If one fails, you are making a wire-breaking change. [Theory] [InlineData(typeof(ReplicationOperation), "ZB.MOM.WW.ScadaBridge.StoreAndForward.ReplicationOperation")] [InlineData(typeof(StoreAndForwardMessage), "ZB.MOM.WW.ScadaBridge.StoreAndForward.StoreAndForwardMessage")] public void ReplicationContract_TypeIdentity_IsPinned(Type type, string expectedFullName) => Assert.Equal(expectedFullName, type.FullName); }