83 lines
3.8 KiB
C#
83 lines
3.8 KiB
C#
using Akka.TestKit.Xunit2;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests;
|
|
|
|
/// <summary>
|
|
/// Characterization pin for the intra-cluster S&F replication contract. A
|
|
/// <see cref="ReplicationOperation"/> (carrying a full <see cref="StoreAndForwardMessage"/>)
|
|
/// 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.
|
|
/// </summary>
|
|
public class ReplicationWireSerializationPinTests : TestKit
|
|
{
|
|
private T RoundTrip<T>(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);
|
|
}
|