using System; using Google.Protobuf.Collections; using ZB.MOM.WW.MxGateway.Contracts.Proto; using ZB.MOM.WW.MxGateway.Worker.MxAccess; namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess; /// /// Unit tests for . The cache is /// consumed by the write command executor's bounded pump-wait so a /// WriteSecured/WriteSecured2 reply can carry the correlated /// OnWriteComplete outcome; its version-baseline contract is exercised in /// isolation here before the STA / COM plumbing gets layered on top. /// public sealed class MxAccessWriteCompletionCacheTests { /// Verifies that Record bumps the version per key and keys stay isolated. [Fact] public void Record_IncrementsVersionPerKey() { MxAccessWriteCompletionCache cache = new(); Assert.Equal(0UL, cache.CurrentVersion(7, 21)); cache.Record(7, 21, BuildStatuses(detail: 100)); Assert.Equal(1UL, cache.CurrentVersion(7, 21)); cache.Record(7, 21, BuildStatuses(detail: 200)); Assert.Equal(2UL, cache.CurrentVersion(7, 21)); cache.Record(7, 22, BuildStatuses(detail: 300)); Assert.Equal(1UL, cache.CurrentVersion(7, 22)); Assert.Equal(2UL, cache.CurrentVersion(7, 21)); } /// Verifies that a completion newer than the baseline is returned with its status rows. [Fact] public void TryWaitForCompletion_WhenCompletionNewerThanBaseline_ReturnsStatuses() { MxAccessWriteCompletionCache cache = new(); cache.Record(7, 21, BuildStatuses(detail: 4321)); bool found = cache.TryWaitForCompletion( 7, 21, sinceVersion: 0UL, deadlineUtc: DateTime.UtcNow.AddSeconds(5), pumpStep: static () => { }, out RepeatedField statuses); Assert.True(found); MxStatusProxy row = Assert.Single(statuses); Assert.Equal(4321, row.Detail); Assert.Equal(MxStatusCategory.Ok, row.Category); } /// /// Verifies that a completion recorded before the baseline was captured is /// never misattributed to the waiting write: only a strictly newer version /// satisfies the wait, so a stale row times the wait out. /// [Fact] public void TryWaitForCompletion_WhenOnlyStaleCompletion_TimesOut() { MxAccessWriteCompletionCache cache = new(); cache.Record(7, 21, BuildStatuses(detail: 4321)); ulong baseline = cache.CurrentVersion(7, 21); bool found = cache.TryWaitForCompletion( 7, 21, sinceVersion: baseline, deadlineUtc: DateTime.UtcNow.AddMilliseconds(50), pumpStep: static () => { }, out RepeatedField statuses); Assert.False(found); Assert.Empty(statuses); } /// /// Verifies the pump loop is what lets a completion land: the completion is /// recorded from inside a later pump step (standing in for the STA /// dispatching the OnWriteComplete message) and the wait then succeeds. /// [Fact] public void TryWaitForCompletion_InvokesPumpStepEachIteration() { MxAccessWriteCompletionCache cache = new(); int pumpCalls = 0; bool found = cache.TryWaitForCompletion( 7, 21, sinceVersion: 0UL, deadlineUtc: DateTime.UtcNow.AddSeconds(5), pumpStep: () => { pumpCalls++; if (pumpCalls == 2) { cache.Record(7, 21, BuildStatuses(detail: 55)); } }, out RepeatedField statuses); Assert.True(found); Assert.True(pumpCalls >= 2); Assert.Equal(55, Assert.Single(statuses).Detail); } /// Verifies that Record stores an independent clone of the caller's rows. [Fact] public void Record_ClonesStatuses() { MxAccessWriteCompletionCache cache = new(); RepeatedField callerRows = BuildStatuses(detail: 77); cache.Record(7, 21, callerRows); callerRows[0].Detail = 999; callerRows.Add(new MxStatusProxy()); Assert.True(cache.TryWaitForCompletion( 7, 21, sinceVersion: 0UL, deadlineUtc: DateTime.UtcNow.AddSeconds(5), pumpStep: static () => { }, out RepeatedField statuses)); Assert.Equal(77, Assert.Single(statuses).Detail); } private static RepeatedField BuildStatuses(int detail) { return new RepeatedField { new MxStatusProxy { Success = 1, Category = MxStatusCategory.Ok, Detail = detail, }, }; } }