From d160c7f69468b4e8e023f492672a840d2f3bca2c Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 16 Jun 2026 06:15:26 -0400 Subject: [PATCH] =?UTF-8?q?test(communication):=20M2.11=20review=20nits=20?= =?UTF-8?q?=E2=80=94=20bridge-actor=20not-found=20test=20+=20dead-letter?= =?UTF-8?q?=20comment=20+=20toast=20wording=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add DebugStreamBridgeActorTests: On_InstanceNotFound_Snapshot_Forwards_To_OnEvent_Does_Not_Open_Stream_And_Terminates — asserts _onEvent receives the not-found snapshot, SubscribeCalls remains empty, and the actor terminates cleanly via Watch/ExpectTerminated. - Add comment in DebugStreamBridgeActor near Context.Stop(Self) explaining that the subsequent StopDebugStream Tell from DebugStreamService.StopStream produces a benign expected dead-letter. - Reword not-found toast in DebugView.razor to "Instance not found on the selected site — check the deployment target." (accurate when the instance may be deployed to a different site). --- .../Pages/Deployment/DebugView.razor | 3 +- .../Actors/DebugStreamBridgeActor.cs | 4 ++ .../Grpc/DebugStreamBridgeActorTests.cs | 38 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor index 7a98209b..0d616cf6 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor @@ -451,8 +451,7 @@ { DebugStreamService.StopStream(session.SessionId); _toast.ShowError( - $"Instance is not deployed on that site. " + - $"Deploy it first or choose the correct site."); + "Instance not found on the selected site — check the deployment target."); _connecting = false; return; } diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs index 7ac30fc1..8e0ecb1b 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/DebugStreamBridgeActor.cs @@ -98,6 +98,10 @@ public class DebugStreamBridgeActor : ReceiveActor, IWithTimers _instanceUniqueName); _stopped = true; _onEvent(snapshot); // resolves the snapshot TCS with InstanceNotFound=true + // Note: after Context.Stop(Self) below the actor is dead. DebugStreamService + // inspects InitialSnapshot.InstanceNotFound and calls StopStream, which sends + // a StopDebugStream message. That Tell arrives after the actor has already + // stopped, producing a benign Akka dead-letter — expected and harmless. Context.Stop(Self); return; } diff --git a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/DebugStreamBridgeActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/DebugStreamBridgeActorTests.cs index 00a0381a..bfbdf338 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/DebugStreamBridgeActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/DebugStreamBridgeActorTests.cs @@ -60,6 +60,44 @@ public class DebugStreamBridgeActorTests : TestKit return new TestContext(actor, commProbe, mockClient, events, terminated); } + [Fact] + public void On_InstanceNotFound_Snapshot_Forwards_To_OnEvent_Does_Not_Open_Stream_And_Terminates() + { + // M2.11: when the site reports InstanceNotFound=true the bridge actor must + // (a) forward the not-found snapshot to _onEvent so DebugStreamService's TCS + // resolves and the caller can inspect the flag, + // (b) NOT open a gRPC stream (SubscribeCalls must remain empty), and + // (c) stop itself cleanly. + var ctx = CreateBridgeActor(); + ctx.CommProbe.ExpectMsg(); // initial subscribe envelope + + var notFoundSnapshot = new DebugViewSnapshot( + InstanceName, + new List(), + new List(), + DateTimeOffset.UtcNow, + InstanceNotFound: true); + + Watch(ctx.BridgeActor); + ctx.BridgeActor.Tell(notFoundSnapshot); + + // (a) _onEvent must receive the not-found snapshot + AwaitCondition(() => { lock (ctx.ReceivedEvents) { return ctx.ReceivedEvents.Count == 1; } }, + TimeSpan.FromSeconds(3)); + lock (ctx.ReceivedEvents) + { + var received = Assert.IsType(ctx.ReceivedEvents[0]); + Assert.True(received.InstanceNotFound); + } + + // (b) no gRPC stream opened + ExpectTerminated(ctx.BridgeActor, TimeSpan.FromSeconds(3)); + Assert.Empty(ctx.MockGrpcClient.SubscribeCalls); + + // (c) actor terminates cleanly + // ExpectTerminated above already verified termination + } + [Fact] public void PreStart_Sends_SubscribeDebugViewRequest_Via_ClusterClient() {