diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs index 41f06bf8..cdcb99a3 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/ArtifactDeploymentService.cs @@ -2,6 +2,7 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment; +using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts; @@ -232,6 +233,38 @@ public class ArtifactDeploymentService if (sites.Count == 0) return Result.Failure("No sites configured."); + return await DeployCoreAsync(sites, user, cancellationToken); + } + + /// + /// Deploys system-wide artifacts to a single site (management-API --site-id path). + /// + /// The database identifier of the target site. + /// The user initiating the deployment. + /// Cancellation token. + /// A summary of the artifact deployment result for the single site. + public async Task> DeployToSiteAsync( + int siteId, + string user, + CancellationToken cancellationToken = default) + { + var site = await _siteRepo.GetSiteByIdAsync(siteId, cancellationToken); + if (site is null) + return Result.Failure($"Site with ID {siteId} not found."); + + return await DeployCoreAsync([site], user, cancellationToken); + } + + /// + /// Core deployment path shared by the all-sites and single-site entry points. + /// Builds a per-site command with that site's data connections and deploys to + /// every supplied site under one logical deployment id. + /// + private async Task> DeployCoreAsync( + IReadOnlyList sites, + string user, + CancellationToken cancellationToken) + { var deploymentId = Guid.NewGuid().ToString("N"); var perSiteResults = new Dictionary(); diff --git a/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs b/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs index fe41d646..4726a646 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/ArtifactDeploymentServiceTests.cs @@ -233,6 +233,41 @@ public class ArtifactDeploymentServiceTests : TestKit Arg.Any(), "retry-site", Arg.Any(), Arg.Any()); } + // ── PLAN-07 Task 4: single-site artifact deployment ── + + [Fact] + public async Task DeployToSiteAsync_DeploysOnlyToTargetSite() + { + // Two sites exist; deploying to site 1 must build/send exactly one per-site + // command (for SITE1) and never touch SITE2's per-site data. + var site1 = new Site("Site One", "SITE1") { Id = 1 }; + _siteRepo.GetSiteByIdAsync(1, Arg.Any()).Returns(site1); + var recorder = new ArtifactProbeRecorder(); + var probe = Sys.ActorOf(Props.Create(() => new ArtifactProbeActor(recorder))); + var service = CreateServiceWithCommActor(probe); + + var result = await service.DeployToSiteAsync(1, "tester"); + + Assert.True(result.IsSuccess); + Assert.Single(result.Value.SiteResults); + Assert.Equal("SITE1", result.Value.SiteResults[0].SiteId); + Assert.Single(recorder.Received); + await _siteRepo.Received(1).GetDataConnectionsBySiteIdAsync(1, Arg.Any()); + await _siteRepo.DidNotReceive().GetDataConnectionsBySiteIdAsync(2, Arg.Any()); + } + + [Fact] + public async Task DeployToSiteAsync_UnknownSite_ReturnsFailure() + { + _siteRepo.GetSiteByIdAsync(999, Arg.Any()).Returns((Site?)null); + var service = CreateService(); + + var result = await service.DeployToSiteAsync(999, "tester"); + + Assert.True(result.IsFailure); + Assert.Contains("999", result.Error); + } + private ArtifactDeploymentService CreateService() { var comms = new CommunicationService(