Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/ResyncWireSerializationPinTests.cs
T

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);
}