Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/SiteGrpcEndpointValidationTests.cs
T

81 lines
3.4 KiB
C#

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));
}
}