using Moq; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.TemplateEngine.Services; namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests; /// /// Review 2026-10-21 01/SR-C1 (central half): names that become Akka actor names at /// the site — the instance unique name (the Instance Actor), and the script / alarm / /// composition-slot names that make up the script-/alarm-/ /// native-alarm- child canonical names — are rejected at save with a readable /// error, instead of crashing the site's Deployment Manager / failing the deploy when /// ActorOf throws InvalidActorNameException. /// public class ActorNameValidationTests { private readonly Mock _repoMock = new(); private readonly Mock _auditMock = new(); private TemplateService TemplateSut => new(_repoMock.Object, _auditMock.Object); /// Verifies that an instance unique name with invalid actor-name characters is rejected. /// An instance name Akka cannot use as an actor name. /// A task that represents the asynchronous operation. [Theory] [InlineData("Furnace #3")] [InlineData("Line 1")] [InlineData("Ofen Süd")] [InlineData("a/b")] [InlineData("$system")] public async Task CreateInstance_InvalidActorName_FailsBeforeAnyWrite(string name) { var sut = new InstanceService(_repoMock.Object, _auditMock.Object); var result = await sut.CreateInstanceAsync(name, 1, 1, null, "admin"); Assert.True(result.IsFailure); Assert.Contains("not allowed", result.Error); _repoMock.Verify(r => r.AddInstanceAsync(It.IsAny(), It.IsAny()), Times.Never); } /// Verifies that a script name with invalid actor-name characters is rejected. /// A task that represents the asynchronous operation. [Fact] public async Task AddScript_InvalidActorName_Fails() { var result = await TemplateSut.AddScriptAsync(1, new TemplateScript("Calc #1", "return 1;"), "admin"); Assert.True(result.IsFailure); Assert.Contains("Calc #1", result.Error); _repoMock.Verify(r => r.AddTemplateScriptAsync(It.IsAny(), It.IsAny()), Times.Never); } /// Verifies that an alarm name with invalid actor-name characters is rejected. /// A task that represents the asynchronous operation. [Fact] public async Task AddAlarm_InvalidActorName_Fails() { var result = await TemplateSut.AddAlarmAsync(1, new TemplateAlarm("High Temp"), "admin"); Assert.True(result.IsFailure); Assert.Contains("High Temp", result.Error); _repoMock.Verify(r => r.AddTemplateAlarmAsync(It.IsAny(), It.IsAny()), Times.Never); } /// Verifies that a composition slot name with invalid actor-name characters is rejected. /// A task that represents the asynchronous operation. [Fact] public async Task AddComposition_InvalidActorName_Fails() { var result = await TemplateSut.AddCompositionAsync(1, 2, "Left Sensor", "admin"); Assert.True(result.IsFailure); Assert.Contains("Left Sensor", result.Error); } /// Verifies that renaming a composition slot to an invalid actor name is rejected. /// A task that represents the asynchronous operation. [Fact] public async Task RenameComposition_InvalidActorName_Fails() { var result = await TemplateSut.RenameCompositionAsync(50, "new slot", "admin"); Assert.True(result.IsFailure); Assert.Contains("new slot", result.Error); } }