feat(scripts): reset-aware Attributes.WriteBatchAndWaitAsync overload + deploy-gate mirror (#36)

Claude-Session: https://claude.ai/code/session_01XPsuy2ZZ5Gbyfpt8PrK5BV
This commit is contained in:
Joseph Doherty
2026-09-10 15:27:00 -04:00
parent 03c8a01dd1
commit b623fdb05e
3 changed files with 101 additions and 0 deletions
@@ -294,4 +294,51 @@ public class AttributeAccessorWaitAsyncTests : TestKit, IDisposable
Assert.True(result.TimedOut);
Assert.Null(result.Value);
}
// ── Reset-aware WriteBatchAndWaitAsync overload (Gitea #36) ──────────────
/// <summary>
/// The reset-aware overload must scope-resolve and codec-encode the reset key/value
/// exactly like every other accessor, must default <c>resetTimeout</c> to
/// <c>timeout</c>, and — when the reset echo never arrives — must return a
/// <see cref="HandshakeResult.ResetFailed"/> result WITHOUT writing the batch or
/// the trigger (the ExpectNoMsg at the end is that assertion).
/// </summary>
[Fact]
public async Task WriteBatchAndWaitAsync_ResetAware_ResolvesScopeAndDefaultsResetTimeout()
{
var probe = CreateTestProbe();
var ctx = MakeContext(probe.Ref);
// Composed scope "RightMESReceiver" — every key is prefixed before it leaves the accessor.
var acc = new AttributeAccessor(ctx, "RightMESReceiver");
var call = acc.WriteBatchAndWaitAsync(
new Dictionary<string, object?> { ["MoveOutBatchID"] = 28950 },
"MoveOutFlag", true, "MoveOutCompleteFlag", true, TimeSpan.FromSeconds(25),
"MoveOutCompleteFlag", false);
// Leg 1 — the reset write.
var reset = probe.ExpectMsg<SetStaticAttributeCommand>(TimeSpan.FromSeconds(5));
Assert.Equal("RightMESReceiver.MoveOutCompleteFlag", reset.AttributeName);
Assert.Equal(AttributeValueCodec.Encode(false), reset.Value);
probe.Reply(new SetStaticAttributeResponse(
reset.CorrelationId, "Pump1", reset.AttributeName, true, null, DateTimeOffset.UtcNow));
// Leg 2 — the reset echo wait, bounded by the defaulted resetTimeout.
var resetWait = probe.ExpectMsg<WaitForAttributeRequest>(TimeSpan.FromSeconds(5));
Assert.Equal("RightMESReceiver.MoveOutCompleteFlag", resetWait.AttributeName);
Assert.Equal(AttributeValueCodec.Encode(false), resetWait.TargetValueEncoded);
Assert.Equal(TimeSpan.FromSeconds(25), resetWait.Timeout); // defaulted to `timeout`
probe.Reply(new WaitForAttributeResponse(
resetWait.CorrelationId, Matched: false, Value: null, Quality: null, TimedOut: true));
var result = await call;
Assert.True(result.ResetFailed);
Assert.False(result.Matched);
Assert.True(result.TimedOut);
// Nothing else was written: no batch, no trigger.
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
}
}