fix(siteruntime): M2.11 — unknown-instance debug snapshot returns InstanceNotFound=true (#24)
RouteDebugSnapshot and RouteDebugViewSubscribe on DeploymentManagerActor
previously returned an empty DebugViewSnapshot for unknown instances,
indistinguishable from a deployed-but-empty instance. Callers had no way
to differentiate "not deployed here" from "deployed, no data yet."
Approach — additive field on existing message contract:
Added `bool InstanceNotFound = false` as an optional trailing parameter
to DebugViewSnapshot (Commons). All existing positional constructor calls
and serialized wire frames are unaffected (default = false). A dedicated
new message type was considered but rejected: the ClusterClient channel
and DebugStreamService TCS are already typed on DebugViewSnapshot, and a
second reply union would require wider changes for zero additive-safety
gain.
Changes:
- Commons/DebugViewSnapshot: add InstanceNotFound = false (additive)
- DeploymentManagerActor: set InstanceNotFound=true in both unknown-
instance branches (RouteDebugViewSubscribe, RouteDebugSnapshot)
- DebugStreamBridgeActor: when snapshot.InstanceNotFound, forward it to
_onEvent (resolves the TCS) then stop cleanly; no gRPC stream opened
- DebugView.razor: check session.InitialSnapshot.InstanceNotFound after
connect and show a clear "not deployed on this site" error toast
- 3 new tests in DeploymentManagerActorTests covering: unknown→snapshot,
unknown→subscribe, known-empty→InstanceNotFound stays false
This commit is contained in:
@@ -3,6 +3,7 @@ using Akka.TestKit.Xunit2;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
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.Enums;
|
||||
@@ -387,4 +388,64 @@ public class DeploymentManagerActorTests : TestKit, IDisposable
|
||||
Assert.Equal("route-corr-2", response.CorrelationId);
|
||||
Assert.True(response.Success, $"Routed call failed: {response.ErrorMessage}");
|
||||
}
|
||||
|
||||
// ── M2.11: Debug-view routing — unknown-instance not-found signal ──
|
||||
|
||||
[Fact]
|
||||
public async Task RouteDebugSnapshot_UnknownInstance_SetsInstanceNotFound()
|
||||
{
|
||||
// An instance that was never deployed to this site must return a
|
||||
// DebugViewSnapshot with InstanceNotFound=true so the caller can
|
||||
// distinguish "not deployed here" from a deployed-but-empty instance.
|
||||
var actor = CreateDeploymentManager();
|
||||
await Task.Delay(500);
|
||||
|
||||
actor.Tell(new DebugSnapshotRequest("NeverDeployed", "snap-corr-1"));
|
||||
|
||||
var snapshot = ExpectMsg<DebugViewSnapshot>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("NeverDeployed", snapshot.InstanceUniqueName);
|
||||
Assert.True(snapshot.InstanceNotFound,
|
||||
"Expected InstanceNotFound=true for an instance not registered on this site.");
|
||||
Assert.Empty(snapshot.AttributeValues);
|
||||
Assert.Empty(snapshot.AlarmStates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteDebugViewSubscribe_UnknownInstance_SetsInstanceNotFound()
|
||||
{
|
||||
// Same contract for the subscribe path: unknown instance → InstanceNotFound=true.
|
||||
var actor = CreateDeploymentManager();
|
||||
await Task.Delay(500);
|
||||
|
||||
actor.Tell(new SubscribeDebugViewRequest("NeverDeployed2", "sub-corr-1"));
|
||||
|
||||
var snapshot = ExpectMsg<DebugViewSnapshot>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("NeverDeployed2", snapshot.InstanceUniqueName);
|
||||
Assert.True(snapshot.InstanceNotFound,
|
||||
"Expected InstanceNotFound=true for an instance not registered on this site.");
|
||||
Assert.Empty(snapshot.AttributeValues);
|
||||
Assert.Empty(snapshot.AlarmStates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteDebugSnapshot_KnownButEmptyInstance_DoesNotSetInstanceNotFound()
|
||||
{
|
||||
// A deployed instance with no runtime data yet must return an empty
|
||||
// snapshot with InstanceNotFound=false — the known-empty path is unchanged.
|
||||
var actor = CreateDeploymentManager();
|
||||
await Task.Delay(500);
|
||||
|
||||
actor.Tell(new DeployInstanceCommand(
|
||||
"dep-dbg", "EmptyPump", "sha256:dbg",
|
||||
MakeConfigJson("EmptyPump"), "admin", DateTimeOffset.UtcNow));
|
||||
ExpectMsg<DeploymentStatusResponse>(TimeSpan.FromSeconds(5));
|
||||
await Task.Delay(800); // let InstanceActor spin up
|
||||
|
||||
actor.Tell(new DebugSnapshotRequest("EmptyPump", "snap-corr-2"));
|
||||
|
||||
var snapshot = ExpectMsg<DebugViewSnapshot>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("EmptyPump", snapshot.InstanceUniqueName);
|
||||
Assert.False(snapshot.InstanceNotFound,
|
||||
"A deployed (but empty) instance must NOT set InstanceNotFound.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user