From 3bf1d26d79bb5db0ed9cda8e7b429f78099ee506 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 31 May 2026 02:23:17 -0400 Subject: [PATCH] feat(management): handlers for native alarm source CRUD --- .../ManagementActor.cs | 110 ++++++++++++++++++ .../ManagementActorTests.cs | 81 +++++++++++++ 2 files changed, 191 insertions(+) diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs index 5dfcb5af..1af5ba32 100644 --- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs @@ -183,6 +183,7 @@ public class ManagementActor : ReceiveActor or DeleteDataConnectionCommand or AddTemplateAttributeCommand or UpdateTemplateAttributeCommand or DeleteTemplateAttributeCommand or AddTemplateAlarmCommand or UpdateTemplateAlarmCommand or DeleteTemplateAlarmCommand + or AddTemplateNativeAlarmSourceCommand or UpdateTemplateNativeAlarmSourceCommand or DeleteTemplateNativeAlarmSourceCommand or AddTemplateScriptCommand or UpdateTemplateScriptCommand or DeleteTemplateScriptCommand or AddTemplateCompositionCommand or DeleteTemplateCompositionCommand or CreateSharedScriptCommand or UpdateSharedScriptCommand or DeleteSharedScriptCommand @@ -204,6 +205,7 @@ public class ManagementActor : ReceiveActor or MgmtDisableInstanceCommand or MgmtDeleteInstanceCommand or SetConnectionBindingsCommand or SetInstanceOverridesCommand or SetInstanceAreaCommand or SetInstanceAlarmOverrideCommand or DeleteInstanceAlarmOverrideCommand + or SetInstanceNativeAlarmSourceOverrideCommand or DeleteInstanceNativeAlarmSourceOverrideCommand or GetDeploymentDiffCommand or MgmtDeployArtifactsCommand or QueryDeploymentsCommand @@ -233,6 +235,10 @@ public class ManagementActor : ReceiveActor AddTemplateAlarmCommand cmd => await HandleAddAlarm(sp, cmd, user.Username), UpdateTemplateAlarmCommand cmd => await HandleUpdateAlarm(sp, cmd, user.Username), DeleteTemplateAlarmCommand cmd => await HandleDeleteAlarm(sp, cmd, user.Username), + AddTemplateNativeAlarmSourceCommand cmd => await HandleAddNativeAlarmSource(sp, cmd), + UpdateTemplateNativeAlarmSourceCommand cmd => await HandleUpdateNativeAlarmSource(sp, cmd), + DeleteTemplateNativeAlarmSourceCommand cmd => await HandleDeleteNativeAlarmSource(sp, cmd), + ListTemplateNativeAlarmSourcesCommand cmd => await HandleListNativeAlarmSources(sp, cmd), AddTemplateScriptCommand cmd => await HandleAddScript(sp, cmd, user.Username), UpdateTemplateScriptCommand cmd => await HandleUpdateScript(sp, cmd, user.Username), DeleteTemplateScriptCommand cmd => await HandleDeleteScript(sp, cmd, user.Username), @@ -261,6 +267,9 @@ public class ManagementActor : ReceiveActor SetInstanceAlarmOverrideCommand cmd => await HandleSetInstanceAlarmOverride(sp, cmd, user), DeleteInstanceAlarmOverrideCommand cmd => await HandleDeleteInstanceAlarmOverride(sp, cmd, user), ListInstanceAlarmOverridesCommand cmd => await HandleListInstanceAlarmOverrides(sp, cmd, user), + SetInstanceNativeAlarmSourceOverrideCommand cmd => await HandleSetInstanceNativeAlarmSourceOverride(sp, cmd, user), + DeleteInstanceNativeAlarmSourceOverrideCommand cmd => await HandleDeleteInstanceNativeAlarmSourceOverride(sp, cmd, user), + ListInstanceNativeAlarmSourceOverridesCommand cmd => await HandleListInstanceNativeAlarmSourceOverrides(sp, cmd, user), // Sites ListSitesCommand => await HandleListSites(sp, user), @@ -765,6 +774,58 @@ public class ManagementActor : ReceiveActor return await repo.GetAlarmOverridesByInstanceIdAsync(cmd.InstanceId); } + private static async Task HandleSetInstanceNativeAlarmSourceOverride( + IServiceProvider sp, SetInstanceNativeAlarmSourceOverrideCommand cmd, AuthenticatedUser user) + { + await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId); + var repo = sp.GetRequiredService(); + + var existing = await repo.GetNativeAlarmSourceOverrideAsync(cmd.InstanceId, cmd.SourceCanonicalName); + if (existing == null) + { + var ovr = new InstanceNativeAlarmSourceOverride(cmd.SourceCanonicalName) + { + InstanceId = cmd.InstanceId, + ConnectionNameOverride = cmd.ConnectionNameOverride, + SourceReferenceOverride = cmd.SourceReferenceOverride, + ConditionFilterOverride = cmd.ConditionFilterOverride + }; + await repo.AddInstanceNativeAlarmSourceOverrideAsync(ovr); + await repo.SaveChangesAsync(); + return ovr; + } + + existing.ConnectionNameOverride = cmd.ConnectionNameOverride; + existing.SourceReferenceOverride = cmd.SourceReferenceOverride; + existing.ConditionFilterOverride = cmd.ConditionFilterOverride; + await repo.UpdateInstanceNativeAlarmSourceOverrideAsync(existing); + await repo.SaveChangesAsync(); + return existing; + } + + private static async Task HandleDeleteInstanceNativeAlarmSourceOverride( + IServiceProvider sp, DeleteInstanceNativeAlarmSourceOverrideCommand cmd, AuthenticatedUser user) + { + await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId); + var repo = sp.GetRequiredService(); + + var existing = await repo.GetNativeAlarmSourceOverrideAsync(cmd.InstanceId, cmd.SourceCanonicalName); + if (existing != null) + { + await repo.DeleteInstanceNativeAlarmSourceOverrideAsync(existing.Id); + await repo.SaveChangesAsync(); + } + return cmd.SourceCanonicalName; + } + + private static async Task HandleListInstanceNativeAlarmSourceOverrides( + IServiceProvider sp, ListInstanceNativeAlarmSourceOverridesCommand cmd, AuthenticatedUser user) + { + await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId); + var repo = sp.GetRequiredService(); + return await repo.GetNativeAlarmSourceOverridesByInstanceIdAsync(cmd.InstanceId); + } + private static async Task HandleGetDeploymentDiff(IServiceProvider sp, GetDeploymentDiffCommand cmd, AuthenticatedUser user) { await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId); @@ -1449,6 +1510,55 @@ public class ManagementActor : ReceiveActor return result.IsSuccess ? result.Value : throw new ManagementCommandException(result.Error); } + // ── Native alarm source bindings (read-only mirror; repository-direct CRUD) ── + + private static async Task HandleAddNativeAlarmSource(IServiceProvider sp, AddTemplateNativeAlarmSourceCommand cmd) + { + var repo = sp.GetRequiredService(); + var source = new TemplateNativeAlarmSource(cmd.Name) + { + TemplateId = cmd.TemplateId, + ConnectionName = cmd.ConnectionName, + SourceReference = cmd.SourceReference, + ConditionFilter = cmd.ConditionFilter, + Description = cmd.Description, + IsLocked = cmd.IsLocked + }; + await repo.AddTemplateNativeAlarmSourceAsync(source); + await repo.SaveChangesAsync(); + return source; + } + + private static async Task HandleUpdateNativeAlarmSource(IServiceProvider sp, UpdateTemplateNativeAlarmSourceCommand cmd) + { + var repo = sp.GetRequiredService(); + var source = await repo.GetTemplateNativeAlarmSourceByIdAsync(cmd.NativeAlarmSourceId) + ?? throw new ManagementCommandException($"Native alarm source {cmd.NativeAlarmSourceId} not found."); + source.Name = cmd.Name; + source.ConnectionName = cmd.ConnectionName; + source.SourceReference = cmd.SourceReference; + source.ConditionFilter = cmd.ConditionFilter; + source.Description = cmd.Description; + source.IsLocked = cmd.IsLocked; + await repo.UpdateTemplateNativeAlarmSourceAsync(source); + await repo.SaveChangesAsync(); + return source; + } + + private static async Task HandleDeleteNativeAlarmSource(IServiceProvider sp, DeleteTemplateNativeAlarmSourceCommand cmd) + { + var repo = sp.GetRequiredService(); + await repo.DeleteTemplateNativeAlarmSourceAsync(cmd.NativeAlarmSourceId); + await repo.SaveChangesAsync(); + return cmd.NativeAlarmSourceId; + } + + private static async Task HandleListNativeAlarmSources(IServiceProvider sp, ListTemplateNativeAlarmSourcesCommand cmd) + { + var repo = sp.GetRequiredService(); + return await repo.GetNativeAlarmSourcesByTemplateIdAsync(cmd.TemplateId); + } + private static async Task HandleAddScript(IServiceProvider sp, AddTemplateScriptCommand cmd, string user) { var svc = sp.GetRequiredService(); diff --git a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs index d49a14d0..919cd04d 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs @@ -1416,4 +1416,85 @@ public class ManagementActorTests : TestKit, IDisposable // earlier Modified row's Overwrite action. Assert.Equal(Commons.Types.Transport.ResolutionAction.Skip, dupResolutions[0].Action); } + + // ======================================================================== + // Native alarm source CRUD (Task 21) + // ======================================================================== + + [Fact] + public void AddTemplateNativeAlarmSource_WithDesignRole_ReturnsSuccess() + { + var actor = CreateActor(); + var envelope = Envelope( + new AddTemplateNativeAlarmSourceCommand(1, "Pressure", "Opc", "ns=2;s=T01", null, "desc", false), + "Design"); + + actor.Tell(envelope); + + ExpectMsg(TimeSpan.FromSeconds(5)); + _templateRepo.ReceivedWithAnyArgs(1).AddTemplateNativeAlarmSourceAsync(default!, default); + _templateRepo.ReceivedWithAnyArgs(1).SaveChangesAsync(default); + } + + [Fact] + public void AddTemplateNativeAlarmSource_WithDeploymentRole_ReturnsUnauthorized() + { + var actor = CreateActor(); + var envelope = Envelope( + new AddTemplateNativeAlarmSourceCommand(1, "Pressure", "Opc", "ns=2;s=T01", null, null, false), + "Deployment"); + + actor.Tell(envelope); + + var response = ExpectMsg(TimeSpan.FromSeconds(5)); + Assert.Contains("Design", response.Message); + } + + [Fact] + public void ListTemplateNativeAlarmSources_ReturnsData() + { + _templateRepo.GetNativeAlarmSourcesByTemplateIdAsync(1, Arg.Any()) + .Returns(new List + { + new("Pressure") { Id = 1, TemplateId = 1, ConnectionName = "Opc", SourceReference = "ns=2;s=T01" } + }); + + var actor = CreateActor(); + var envelope = Envelope(new ListTemplateNativeAlarmSourcesCommand(1)); + + actor.Tell(envelope); + + var response = ExpectMsg(TimeSpan.FromSeconds(5)); + Assert.Contains("Pressure", response.JsonData); + } + + [Fact] + public void SetInstanceNativeAlarmSourceOverride_WithDeploymentRole_ReturnsSuccess() + { + // No prior override → Add path. + var actor = CreateActor(); + var envelope = Envelope( + new SetInstanceNativeAlarmSourceOverrideCommand(1, "Pressure", "Opc2", "ns=2;s=NEW", null), + "Deployment"); + + actor.Tell(envelope); + + ExpectMsg(TimeSpan.FromSeconds(5)); + _templateRepo.ReceivedWithAnyArgs(1).AddInstanceNativeAlarmSourceOverrideAsync(default!, default); + _templateRepo.ReceivedWithAnyArgs(1).SaveChangesAsync(default); + } + + [Fact] + public void SetInstanceNativeAlarmSourceOverride_WithDesignRole_ReturnsUnauthorized() + { + var actor = CreateActor(); + var envelope = Envelope( + new SetInstanceNativeAlarmSourceOverrideCommand(1, "Pressure", "Opc2", "ns=2;s=NEW", null), + "Design"); + + actor.Tell(envelope); + + var response = ExpectMsg(TimeSpan.FromSeconds(5)); + Assert.Contains("Deployment", response.Message); + } }