diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs index 61c7017d..aa008556 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs @@ -92,11 +92,6 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers Receive(HandleRegisterLocalHandler); // Pattern 1: Instance Deployment — forward to Deployment Manager - Receive(msg => - { - _log.Debug("Routing DeployInstanceCommand for {0} to DeploymentManager", msg.InstanceUniqueName); - _deploymentManagerProxy.Forward(msg); - }); Receive(msg => { _log.Debug("Routing RefreshDeploymentCommand for {0} to DeploymentManager", msg.InstanceUniqueName); diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs index a7cdfa41..06c6c825 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs @@ -116,29 +116,9 @@ public class CommunicationService // ── Pattern 1: Instance Deployment ── /// - /// Sends a deployment command for an instance to a site. - /// - /// The target site identifier. - /// The deployment command. - /// Cancellation token. - /// The deployment status response. - public async Task DeployInstanceAsync( - string siteId, DeployInstanceCommand command, CancellationToken cancellationToken = default) - { - _logger.LogDebug( - "Sending DeployInstanceCommand to site {SiteId}, instance={Instance}, correlationId={DeploymentId}", - siteId, command.InstanceUniqueName, command.DeploymentId); - - var envelope = new SiteEnvelope(siteId, command); - return await GetActor().Ask( - envelope, _options.DeploymentTimeout, cancellationToken); - } - - /// - /// Sends a small "refresh deployment" notify to a site (notify-and-fetch). - /// Replaces on the wire: the site fetches the - /// config over HTTP rather than receiving it inline. Reply is the existing - /// DeploymentStatusResponse, bounded by the deployment timeout. + /// Sends a small "refresh deployment" notify to a site (notify-and-fetch): + /// the site fetches the config over HTTP rather than receiving it inline. + /// Reply is the existing DeploymentStatusResponse, bounded by the deployment timeout. /// /// The target site identifier. /// The refresh-deployment notify. diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs index 8b97e4b0..b132351e 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/SiteReplicationActor.cs @@ -154,9 +154,9 @@ public class SiteReplicationActor : ReceiveActor { if (string.IsNullOrEmpty(msg.CentralFetchBaseUrl)) { - // The still-present direct DeployInstanceCommand wire path (retired in Task 14) - // replicates with empty coords; there is nothing to fetch. Skip quietly rather - // than calling FetchAsync("") and logging an error — T18 reconciliation backstops. + // The direct DeployInstanceCommand cross-cluster wire path was retired (Task 14). + // This guard is a defensive fallback: skip quietly rather than calling FetchAsync("") + // and logging a spurious error. T18 reconciliation backstops any missed writes. _logger.LogDebug( "No fetch coords for {Instance} (deployment {DeploymentId}) — skipping replicated fetch; T18 reconciliation is the backstop", msg.InstanceName, msg.DeploymentId); diff --git a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CentralCommunicationActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CentralCommunicationActorTests.cs index a7dae8b0..40aeb4ef 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CentralCommunicationActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CentralCommunicationActorTests.cs @@ -81,26 +81,6 @@ public class CentralCommunicationActorTests : TestKit private Site CreateSite(string identifier, string? nodeAAddress, string? nodeBAddress = null) => new("Test Site", identifier) { NodeAAddress = nodeAAddress, NodeBAddress = nodeBAddress }; - [Fact] - public void ClusterClientRouting_RoutesToConfiguredSite() - { - var site = CreateSite("site1", "akka.tcp://scadabridge@host:8082"); - var (actor, _, siteProbes) = CreateActorWithMockRepo(new[] { site }); - - // Wait for auto-refresh (PreStart schedules with TimeSpan.Zero initial delay) - Thread.Sleep(1000); - - var command = new DeployInstanceCommand( - "dep1", "inst1", "hash1", "{}", "admin", DateTimeOffset.UtcNow); - actor.Tell(new SiteEnvelope("site1", command)); - - // The site1 probe (acting as ClusterClient) should receive a ClusterClient.Send - var msg = siteProbes["site1"].ExpectMsg(); - Assert.Equal("/user/site-communication", msg.Path); - Assert.IsType(msg.Message); - Assert.Equal("dep1", ((DeployInstanceCommand)msg.Message).DeploymentId); - } - [Fact] public void ClusterClientRouting_RefreshDeploymentCommand_RoutesToSite() { @@ -128,8 +108,9 @@ public class CentralCommunicationActorTests : TestKit // Wait for auto-refresh Thread.Sleep(1000); - var command = new DeployInstanceCommand( - "dep1", "inst1", "hash1", "{}", "admin", DateTimeOffset.UtcNow); + var command = new RefreshDeploymentCommand( + "dep1", "inst1", "hash1", "admin", DateTimeOffset.UtcNow, + "https://central:9000", "tok1"); actor.Tell(new SiteEnvelope("unknown-site", command)); ExpectNoMsg(TimeSpan.FromMilliseconds(200)); @@ -177,11 +158,12 @@ public class CentralCommunicationActorTests : TestKit Thread.Sleep(1000); // Verify routing to site1 works - var cmd1 = new DeployInstanceCommand( - "dep1", "inst1", "hash1", "{}", "admin", DateTimeOffset.UtcNow); + var cmd1 = new RefreshDeploymentCommand( + "dep1", "inst1", "hash1", "admin", DateTimeOffset.UtcNow, + "https://central:9000", "tok1"); actor.Tell(new SiteEnvelope("site1", cmd1)); var msg1 = siteProbes["site1"].ExpectMsg(); - Assert.Equal("dep1", ((DeployInstanceCommand)msg1.Message).DeploymentId); + Assert.Equal("dep1", ((RefreshDeploymentCommand)msg1.Message).DeploymentId); // Update mock repo to return both sites var site2 = CreateSite("site2", "akka.tcp://scadabridge@host2:8082"); @@ -193,11 +175,12 @@ public class CentralCommunicationActorTests : TestKit Thread.Sleep(1000); // Verify routing to site2 now works - var cmd2 = new DeployInstanceCommand( - "dep2", "inst2", "hash2", "{}", "admin", DateTimeOffset.UtcNow); + var cmd2 = new RefreshDeploymentCommand( + "dep2", "inst2", "hash2", "admin", DateTimeOffset.UtcNow, + "https://central:9000", "tok2"); actor.Tell(new SiteEnvelope("site2", cmd2)); var msg2 = siteProbes["site2"].ExpectMsg(); - Assert.Equal("dep2", ((DeployInstanceCommand)msg2.Message).DeploymentId); + Assert.Equal("dep2", ((RefreshDeploymentCommand)msg2.Message).DeploymentId); } [Fact] @@ -244,14 +227,15 @@ public class CentralCommunicationActorTests : TestKit Thread.Sleep(1000); // good-site must still be registered and routable despite bad-site failing to parse. - var cmd = new DeployInstanceCommand( - "dep1", "inst1", "hash1", "{}", "admin", DateTimeOffset.UtcNow); + var cmd = new RefreshDeploymentCommand( + "dep1", "inst1", "hash1", "admin", DateTimeOffset.UtcNow, + "https://central:9000", "tok1"); actor.Tell(new SiteEnvelope("good-site", cmd)); Assert.True(siteProbes.ContainsKey("good-site"), "good-site should have a ClusterClient even though bad-site's address is malformed"); var msg = siteProbes["good-site"].ExpectMsg(); - Assert.Equal("dep1", ((DeployInstanceCommand)msg.Message).DeploymentId); + Assert.Equal("dep1", ((RefreshDeploymentCommand)msg.Message).DeploymentId); } private NotificationSubmit CreateSubmit(string id = "notif1") => diff --git a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CommunicationServiceTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CommunicationServiceTests.cs index f6a2ae3d..4ad6c706 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CommunicationServiceTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/CommunicationServiceTests.cs @@ -15,20 +15,6 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Tests; /// public class CommunicationServiceTests : TestKit { - [Fact] - public async Task BeforeInitialization_ThrowsOnUsage() - { - var options = Options.Create(new CommunicationOptions()); - var logger = NullLogger.Instance; - var service = new CommunicationService(options, logger); - - // CommunicationService requires SetCommunicationActor before use - await Assert.ThrowsAsync(() => - service.DeployInstanceAsync("site1", - new DeployInstanceCommand( - "dep1", "inst1", "hash1", "{}", "admin", DateTimeOffset.UtcNow))); - } - [Fact] public void UnsubscribeDebugView_IsTellNotAsk() { diff --git a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommunicationActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommunicationActorTests.cs index e1951ad3..64f74793 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommunicationActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/SiteCommunicationActorTests.cs @@ -25,20 +25,6 @@ public class SiteCommunicationActorTests : TestKit { } - [Fact] - public void DeployCommand_ForwardedToDeploymentManager() - { - var dmProbe = CreateTestProbe(); - var siteActor = Sys.ActorOf(Props.Create(() => - new SiteCommunicationActor("site1", _options, dmProbe.Ref))); - - var command = new DeployInstanceCommand( - "dep1", "inst1", "hash1", "{}", "admin", DateTimeOffset.UtcNow); - siteActor.Tell(command); - - dmProbe.ExpectMsg(msg => msg.DeploymentId == "dep1"); - } - [Fact] public void RefreshDeploymentCommand_ForwardedToDeploymentManager() { diff --git a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/SiteReplicationActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/SiteReplicationActorTests.cs index aa530589..d9662d49 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/SiteReplicationActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/SiteReplicationActorTests.cs @@ -142,8 +142,9 @@ akka { [Fact] public async Task ApplyConfigDeploy_EmptyFetchCoords_SkipsFetchAndWrite() { - // The direct DeployInstanceCommand wire path (retired in Task 14) replicates with - // empty coords; the guard must skip quietly — no FetchAsync("") call, no write. + // The direct DeployInstanceCommand cross-cluster wire path was retired in Task 14. + // This tests the defensive guard: if empty coords arrive, the actor must skip quietly + // — no FetchAsync("") call, no write — rather than erroring. var fetcher = new FakeConfigFetcher(_ => Task.FromResult("never")); var actor = CreateReplicationActor(fetcher);