96 lines
4.2 KiB
C#
96 lines
4.2 KiB
C#
using Akka.TestKit.Xunit2;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
|
|
using ZB.MOM.WW.ScadaBridge.StoreAndForward;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests;
|
|
|
|
/// <summary>
|
|
/// Characterization pin for the chunked anti-entropy resync contract (review 02 round 2,
|
|
/// N2). <see cref="SfBufferSnapshotChunk"/> (active→standby) and <see cref="SfBufferResyncAck"/>
|
|
/// (standby→active) ride intra-site Akka remoting on the default reflective-JSON wire
|
|
/// format. A rename/move, dropped setter, or non-default-constructible message would
|
|
/// silently break resync across a rolling upgrade and only surface as a divergent buffer
|
|
/// after a failover. These pin round-trip fidelity and type identity. (These messages are
|
|
/// NOT ClusterClient traffic, so they are intentionally absent from ClusterClientContractLockTests.)
|
|
/// </summary>
|
|
public class ResyncWireSerializationPinTests : 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());
|
|
}
|
|
|
|
private static StoreAndForwardMessage FullMessage() => new()
|
|
{
|
|
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(),
|
|
};
|
|
|
|
[Fact]
|
|
public void SfBufferSnapshotChunk_WithFullMessage_RoundTripsOnTheWire()
|
|
{
|
|
var message = FullMessage();
|
|
var original = new SfBufferSnapshotChunk(
|
|
"resync-1", 2, 5, new List<StoreAndForwardMessage> { message }, Truncated: true);
|
|
|
|
var back = RoundTrip(original);
|
|
|
|
Assert.Equal(original.ResyncId, back.ResyncId);
|
|
Assert.Equal(original.Sequence, back.Sequence);
|
|
Assert.Equal(original.TotalChunks, back.TotalChunks);
|
|
Assert.Equal(original.Truncated, back.Truncated);
|
|
var m = Assert.Single(back.Messages);
|
|
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);
|
|
}
|
|
|
|
[Fact]
|
|
public void SfBufferResyncAck_RoundTripsOnTheWire()
|
|
{
|
|
var original = new SfBufferResyncAck("resync-1", 42);
|
|
|
|
var back = RoundTrip(original);
|
|
|
|
Assert.Equal(original.ResyncId, back.ResyncId);
|
|
Assert.Equal(original.RowCount, back.RowCount);
|
|
}
|
|
|
|
// Type-identity pins: the reflective-JSON wire embeds CLR type manifests, so a
|
|
// rename/move of either type silently breaks resync across a rolling upgrade.
|
|
[Theory]
|
|
[InlineData(typeof(SfBufferSnapshotChunk), "ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors.SfBufferSnapshotChunk")]
|
|
[InlineData(typeof(SfBufferResyncAck), "ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors.SfBufferResyncAck")]
|
|
public void ResyncContract_TypeIdentity_IsPinned(Type type, string expectedFullName) =>
|
|
Assert.Equal(expectedFullName, type.FullName);
|
|
}
|