test(managementservice): MgmtDeployArea in-scope success path relays Force to a real DeploymentService and returns its summary (Gitea #40 review)
This commit is contained in:
@@ -8,6 +8,7 @@ using NSubstitute;
|
|||||||
using ZB.MOM.WW.ScadaBridge.Communication;
|
using ZB.MOM.WW.ScadaBridge.Communication;
|
||||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||||
using NSubstitute.ExceptionExtensions;
|
using NSubstitute.ExceptionExtensions;
|
||||||
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
|
||||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
|
||||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
|
||||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Schemas;
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Schemas;
|
||||||
@@ -90,6 +91,33 @@ public class ManagementActorTests : TestKit, IDisposable
|
|||||||
_services.AddScoped(_ => _artifactService);
|
_services.AddScoped(_ => _artifactService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a REAL <see cref="DeploymentService"/> over substituted repositories
|
||||||
|
/// (its methods are not virtual, so it cannot be substituted itself — the same
|
||||||
|
/// approach the CentralUI bUnit tests take). Area lookups go through the
|
||||||
|
/// fixture's shared <see cref="_templateRepo"/>.
|
||||||
|
/// </summary>
|
||||||
|
private (IDeploymentManagerRepository DeployRepo, IFlatteningPipeline Pipeline, ISiteRepository SiteRepo) RegisterDeploymentService()
|
||||||
|
{
|
||||||
|
var deployRepo = Substitute.For<IDeploymentManagerRepository>();
|
||||||
|
var pipeline = Substitute.For<IFlatteningPipeline>();
|
||||||
|
var siteRepo = Substitute.For<ISiteRepository>();
|
||||||
|
pipeline.CreateSession().Returns(_ => new FlattenSession(new TemplateGraphWatermark()));
|
||||||
|
|
||||||
|
var service = new DeploymentService(
|
||||||
|
deployRepo, siteRepo, _templateRepo, pipeline,
|
||||||
|
new CommunicationService(Options.Create(new CommunicationOptions()), NullLogger<CommunicationService>.Instance),
|
||||||
|
new OperationLockManager(), _auditService,
|
||||||
|
new ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening.DiffService(),
|
||||||
|
new ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening.RevisionHashService(),
|
||||||
|
new DeploymentStatusNotifier(NullLogger<DeploymentStatusNotifier>.Instance),
|
||||||
|
Options.Create(new DeploymentManagerOptions { OperationLockTimeout = TimeSpan.FromSeconds(5) }),
|
||||||
|
Options.Create(new CommunicationOptions()),
|
||||||
|
NullLogger<DeploymentService>.Instance);
|
||||||
|
_services.AddScoped(_ => service);
|
||||||
|
return (deployRepo, pipeline, siteRepo);
|
||||||
|
}
|
||||||
|
|
||||||
private static Result<ArtifactDeploymentSummary> DeploySummary() =>
|
private static Result<ArtifactDeploymentSummary> DeploySummary() =>
|
||||||
Result<ArtifactDeploymentSummary>.Success(
|
Result<ArtifactDeploymentSummary>.Success(
|
||||||
new ArtifactDeploymentSummary("deploy-1", Array.Empty<SiteArtifactResult>(), 1, 0));
|
new ArtifactDeploymentSummary("deploy-1", Array.Empty<SiteArtifactResult>(), 1, 0));
|
||||||
@@ -1449,6 +1477,82 @@ public class ManagementActorTests : TestKit, IDisposable
|
|||||||
ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The in-scope success path of <c>MgmtDeployArea</c>: the handler resolves the
|
||||||
|
/// REAL <see cref="DeploymentService"/> (its methods are not virtual, so it runs
|
||||||
|
/// over substituted repositories), relays the command's <c>Force</c> verbatim to
|
||||||
|
/// <c>DeployAreaAsync</c>, and returns the bulk summary as the success payload.
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// <c>Force</c> is observable without a site: the one instance in the area is
|
||||||
|
/// up-to-date, so unforced it is reported as an up-to-date skip; forced it is
|
||||||
|
/// attempted, and the substituted flatten refuses it, so it comes back as a
|
||||||
|
/// failed row — neither path ever reaches the site.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="force">The command's <c>Force</c> flag.</param>
|
||||||
|
[Theory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void DeployArea_InScopeForSiteScopedUser_RelaysForce_AndReturnsSummary(bool force)
|
||||||
|
{
|
||||||
|
var (deployRepo, pipeline, siteRepo) = RegisterDeploymentService();
|
||||||
|
|
||||||
|
var area = new Area("Line") { Id = 12, SiteId = 1 };
|
||||||
|
_templateRepo.GetAreaByIdAsync(12, Arg.Any<CancellationToken>()).Returns(area);
|
||||||
|
_templateRepo.GetAreasBySiteIdAsync(1, Arg.Any<CancellationToken>())
|
||||||
|
.Returns((IReadOnlyList<Area>)new List<Area> { area });
|
||||||
|
siteRepo.GetSiteByIdAsync(1, Arg.Any<CancellationToken>())
|
||||||
|
.Returns(new Site("Site 1", "site-1") { Id = 1 });
|
||||||
|
|
||||||
|
var instance = new Instance("Pump5") { Id = 5, SiteId = 1, AreaId = 12, TemplateId = 10, State = InstanceState.Enabled };
|
||||||
|
siteRepo.GetInstancesBySiteIdAsync(1, Arg.Any<CancellationToken>())
|
||||||
|
.Returns((IReadOnlyList<Instance>)new List<Instance> { instance });
|
||||||
|
deployRepo.GetInstanceByIdAsync(5, Arg.Any<CancellationToken>()).Returns(instance);
|
||||||
|
|
||||||
|
// Up-to-date: the deployed snapshot's hash equals the freshly recomputed one.
|
||||||
|
var prior = new DeploymentRecord("prior-5", "admin")
|
||||||
|
{
|
||||||
|
InstanceId = 5, Status = DeploymentStatus.Success, RevisionHash = "sha256:5",
|
||||||
|
};
|
||||||
|
deployRepo.GetDeployedRevisionHashesAsync(Arg.Any<CancellationToken>())
|
||||||
|
.Returns((IReadOnlyDictionary<int, string>)new Dictionary<int, string> { [5] = "sha256:5" });
|
||||||
|
deployRepo.GetCurrentDeploymentStatusesAsync(Arg.Any<IReadOnlyCollection<int>>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns((IReadOnlyDictionary<int, DeploymentRecord>)new Dictionary<int, DeploymentRecord> { [5] = prior });
|
||||||
|
deployRepo.GetCurrentDeploymentStatusAsync(5, Arg.Any<CancellationToken>()).Returns(prior);
|
||||||
|
pipeline.ComputeRevisionHashAsync(5, Arg.Any<FlattenSession?>(), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(Result<string>.Success("sha256:5"));
|
||||||
|
pipeline.FlattenAndValidateAsync(5, Arg.Any<CancellationToken>(), Arg.Any<bool>(), Arg.Any<FlattenSession?>())
|
||||||
|
.Returns(Result<FlatteningPipelineResult>.Failure("flatten refused by test"));
|
||||||
|
|
||||||
|
var actor = CreateActor();
|
||||||
|
var envelope = ScopedEnvelope(new MgmtDeployAreaCommand(12, force), new[] { "1" }, "Deployer");
|
||||||
|
|
||||||
|
actor.Tell(envelope);
|
||||||
|
|
||||||
|
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(10));
|
||||||
|
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||||
|
var summary = JsonSerializer.Deserialize<SiteDeploymentSummary>(
|
||||||
|
response.JsonData, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!;
|
||||||
|
Assert.Equal("site-1", summary.SiteIdentifier);
|
||||||
|
|
||||||
|
if (force)
|
||||||
|
{
|
||||||
|
var row = Assert.Single(summary.InstanceResults);
|
||||||
|
Assert.Equal("Pump5", row.UniqueName);
|
||||||
|
Assert.False(row.Success);
|
||||||
|
Assert.Equal(1, summary.FailureCount);
|
||||||
|
Assert.Empty(summary.SkippedInstances ?? []);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Empty(summary.InstanceResults);
|
||||||
|
var skipped = Assert.Single(summary.SkippedInstances!);
|
||||||
|
Assert.Equal("Pump5", skipped.UniqueName);
|
||||||
|
Assert.Contains("up-to-date", skipped.Reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that get instance, when in scope for site scoped user, returns success.</summary>
|
/// <summary>Verifies that get instance, when in scope for site scoped user, returns success.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetInstance_InScopeForSiteScopedUser_ReturnsSuccess()
|
public void GetInstance_InScopeForSiteScopedUser_ReturnsSuccess()
|
||||||
|
|||||||
Reference in New Issue
Block a user