fix(management): validate site gRPC addresses on create/update (review 2026-10-21 03/R-1)
This commit is contained in:
+80
@@ -0,0 +1,80 @@
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
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.Management;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Review 2026-10-21 03/R-1: a malformed site gRPC address used to be persisted
|
||||
/// unvalidated and then throw inside <c>SitePairChannelProvider.UpdateSite</c>.
|
||||
/// Create/Update must reject it at the write boundary with a readable error.
|
||||
/// </summary>
|
||||
public class SiteGrpcEndpointValidationTests : TestKit
|
||||
{
|
||||
private readonly ISiteRepository _siteRepo = Substitute.For<ISiteRepository>();
|
||||
private readonly ServiceCollection _services;
|
||||
|
||||
/// <summary>Initializes the harness.</summary>
|
||||
public SiteGrpcEndpointValidationTests()
|
||||
{
|
||||
_services = new ServiceCollection().AddTestConfigurationTransaction();
|
||||
_services.AddScoped(_ => _siteRepo);
|
||||
_services.AddScoped(_ => Substitute.For<IAuditService>());
|
||||
_siteRepo.GetSiteByIdAsync(7, Arg.Any<CancellationToken>())
|
||||
.Returns(new Site("S", "S1") { Id = 7 });
|
||||
}
|
||||
|
||||
private IActorRef CreateActor() =>
|
||||
Sys.ActorOf(Props.Create(() => new ManagementActor(
|
||||
_services.BuildServiceProvider(), NullLogger<ManagementActor>.Instance)));
|
||||
|
||||
private static ManagementEnvelope Admin(object command) =>
|
||||
new(new AuthenticatedUser("admin", "Admin", new[] { "Administrator" }, Array.Empty<string>()),
|
||||
command, Guid.NewGuid().ToString("N"));
|
||||
|
||||
/// <summary>Create with a scheme-less node-A address is rejected and nothing is saved.</summary>
|
||||
[Theory]
|
||||
[InlineData("scadabridge-site-a-node-a")]
|
||||
[InlineData("site-a-node-a:8083")]
|
||||
[InlineData("ftp://host:8083")]
|
||||
public void CreateSite_MalformedGrpcAddress_ReturnsReadableError(string bad)
|
||||
{
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Admin(new CreateSiteCommand("S", "S1", null, GrpcNodeAAddress: bad)));
|
||||
|
||||
var error = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("GrpcNodeAAddress", error.Error);
|
||||
Assert.Contains("http://", error.Error);
|
||||
_siteRepo.DidNotReceive().AddSiteAsync(Arg.Any<Site>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Update with a malformed node-B address is rejected and nothing is saved.</summary>
|
||||
[Fact]
|
||||
public void UpdateSite_MalformedGrpcNodeB_ReturnsReadableError()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Admin(new UpdateSiteCommand(7, "S", null,
|
||||
GrpcNodeAAddress: "http://a:8083", GrpcNodeBAddress: "b:8083")));
|
||||
|
||||
var error = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Contains("GrpcNodeBAddress", error.Error);
|
||||
_siteRepo.DidNotReceive().UpdateSiteAsync(Arg.Any<Site>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Both gRPC addresses are optional; valid http(s) URLs (and blanks) are accepted.</summary>
|
||||
[Fact]
|
||||
public void CreateSite_ValidOrEmptyGrpcAddresses_Succeeds()
|
||||
{
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Admin(new CreateSiteCommand("S", "S1", null,
|
||||
GrpcNodeAAddress: "http://site-a-node-a:8083", GrpcNodeBAddress: null)));
|
||||
|
||||
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user