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.Templates; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests; /// /// Review 2026-10-21 (lane F follow-up to 01/SR-C1): a native alarm source's name becomes /// part of the site actor name native-alarm-{canonical}. Template scripts, alarms and /// composition slots are validated by TemplateService, but native-source CRUD is /// repository-direct in the ManagementActor, so it must apply ActorNameRules itself. /// public class NativeAlarmSourceNameValidationTests : TestKit { private readonly ITemplateEngineRepository _repo = Substitute.For(); private readonly ServiceCollection _services = new ServiceCollection().AddTestConfigurationTransaction(); /// Initializes the harness. public NativeAlarmSourceNameValidationTests() { _services.AddScoped(_ => _repo); _repo.GetTemplateNativeAlarmSourceByIdAsync(4, Arg.Any()) .Returns(new TemplateNativeAlarmSource("Pressure") { Id = 4, TemplateId = 1 }); } private IActorRef CreateActor() => Sys.ActorOf(Props.Create(() => new ManagementActor( _services.BuildServiceProvider(), NullLogger.Instance))); private static ManagementEnvelope Designer(object command) => new(new AuthenticatedUser("d", "D", new[] { "Designer" }, Array.Empty()), command, Guid.NewGuid().ToString("N")); /// Adding a source whose name is not a legal actor name is rejected before any write. [Theory] [InlineData("Tank Pressure")] [InlineData("$system")] [InlineData("a/b")] public void Add_InvalidActorName_IsRejected(string name) { CreateActor().Tell(Designer(new AddTemplateNativeAlarmSourceCommand(1, name, "Opc", "ns=2;s=T01", null, null, false))); var error = ExpectMsg(TimeSpan.FromSeconds(5)); Assert.Contains("Native alarm source name is invalid", error.Error); _repo.DidNotReceive().AddTemplateNativeAlarmSourceAsync(Arg.Any(), Arg.Any()); } /// Renaming a source to an illegal actor name is rejected before any write. [Fact] public void Update_RenameToInvalidActorName_IsRejected() { CreateActor().Tell(Designer(new UpdateTemplateNativeAlarmSourceCommand(4, "Tank Pressure", "Opc", "ns=2;s=T01", null, null, false))); var error = ExpectMsg(TimeSpan.FromSeconds(5)); Assert.Contains("Native alarm source name is invalid", error.Error); _repo.DidNotReceive().UpdateTemplateNativeAlarmSourceAsync(Arg.Any(), Arg.Any()); } }