feat(siteruntime): unpack routed RouteToWaitForAttributeRequest into InstanceActor (spec §6 site half)

This commit is contained in:
Joseph Doherty
2026-06-17 09:10:08 -04:00
parent 61048a4ecf
commit c482cac110
3 changed files with 97 additions and 0 deletions
@@ -6,6 +6,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Actors;
@@ -389,6 +390,61 @@ public class DeploymentManagerActorTests : TestKit, IDisposable
Assert.True(response.Success, $"Routed call failed: {response.ErrorMessage}");
}
// ── Spec §6 (WD-2b): routed RouteToWaitForAttributeRequest → InstanceActor ──
[Fact]
public async Task RouteInboundApiWaitForAttribute_AttributeAlreadyAtTarget_RepliesMatched()
{
// A routed wait whose target equals the instance's current (static)
// attribute value must satisfy the InstanceActor fast-path and come back
// Success:true, Matched:true with the matched value/quality.
var actor = CreateDeploymentManager();
await Task.Delay(500); // empty startup
// MakeConfigJson seeds a scalar static attribute "TestAttr" = "42" (Good).
actor.Tell(new DeployInstanceCommand(
"dep-wait", "WaitPump", "sha256:wait",
MakeConfigJson("WaitPump"), "admin", DateTimeOffset.UtcNow));
ExpectMsg<DeploymentStatusResponse>(TimeSpan.FromSeconds(5));
await Task.Delay(1000); // let the InstanceActor spin up + load static attrs
// Encode the target the same way the InstanceActor encodes the current
// value for its codec-equality match (value-equality only across the wire).
var encodedTarget = AttributeValueCodec.Encode("42");
actor.Tell(new RouteToWaitForAttributeRequest(
"wait-corr-1", "WaitPump", "TestAttr", encodedTarget,
TimeSpan.FromSeconds(5), DateTimeOffset.UtcNow));
var response = ExpectMsg<RouteToWaitForAttributeResponse>(TimeSpan.FromSeconds(10));
Assert.Equal("wait-corr-1", response.CorrelationId);
Assert.True(response.Success, $"Routed wait failed: {response.ErrorMessage}");
Assert.True(response.Matched, "Expected fast-path match (attribute already at target).");
Assert.False(response.TimedOut);
Assert.Equal("42", response.Value);
Assert.Equal("Good", response.Quality);
}
[Fact]
public async Task RouteInboundApiWaitForAttribute_UnknownInstance_RepliesNotFound()
{
// A routed wait for an instance that was never deployed to this site must
// come back Success:false with a not-found message (routing-level outcome),
// mirroring the other RouteTo* unknown-instance paths.
var actor = CreateDeploymentManager();
await Task.Delay(500);
actor.Tell(new RouteToWaitForAttributeRequest(
"wait-corr-2", "NeverDeployedWait", "TestAttr",
AttributeValueCodec.Encode("42"), TimeSpan.FromSeconds(5), DateTimeOffset.UtcNow));
var response = ExpectMsg<RouteToWaitForAttributeResponse>(TimeSpan.FromSeconds(5));
Assert.Equal("wait-corr-2", response.CorrelationId);
Assert.False(response.Success);
Assert.False(response.Matched);
Assert.NotNull(response.ErrorMessage);
Assert.Contains("not found", response.ErrorMessage!, StringComparison.OrdinalIgnoreCase);
}
// ── M2.11: Debug-view routing — unknown-instance not-found signal ──
[Fact]