feat(management): handlers for native alarm source CRUD

This commit is contained in:
Joseph Doherty
2026-05-31 02:23:17 -04:00
parent b1df6d5beb
commit 3bf1d26d79
2 changed files with 191 additions and 0 deletions
@@ -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<object?> HandleSetInstanceNativeAlarmSourceOverride(
IServiceProvider sp, SetInstanceNativeAlarmSourceOverrideCommand cmd, AuthenticatedUser user)
{
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
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<object?> HandleDeleteInstanceNativeAlarmSourceOverride(
IServiceProvider sp, DeleteInstanceNativeAlarmSourceOverrideCommand cmd, AuthenticatedUser user)
{
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
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<object?> HandleListInstanceNativeAlarmSourceOverrides(
IServiceProvider sp, ListInstanceNativeAlarmSourceOverridesCommand cmd, AuthenticatedUser user)
{
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
return await repo.GetNativeAlarmSourceOverridesByInstanceIdAsync(cmd.InstanceId);
}
private static async Task<object?> 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<object?> HandleAddNativeAlarmSource(IServiceProvider sp, AddTemplateNativeAlarmSourceCommand cmd)
{
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
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<object?> HandleUpdateNativeAlarmSource(IServiceProvider sp, UpdateTemplateNativeAlarmSourceCommand cmd)
{
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
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<object?> HandleDeleteNativeAlarmSource(IServiceProvider sp, DeleteTemplateNativeAlarmSourceCommand cmd)
{
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
await repo.DeleteTemplateNativeAlarmSourceAsync(cmd.NativeAlarmSourceId);
await repo.SaveChangesAsync();
return cmd.NativeAlarmSourceId;
}
private static async Task<object?> HandleListNativeAlarmSources(IServiceProvider sp, ListTemplateNativeAlarmSourcesCommand cmd)
{
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
return await repo.GetNativeAlarmSourcesByTemplateIdAsync(cmd.TemplateId);
}
private static async Task<object?> HandleAddScript(IServiceProvider sp, AddTemplateScriptCommand cmd, string user)
{
var svc = sp.GetRequiredService<TemplateService>();
@@ -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<ManagementSuccess>(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<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
Assert.Contains("Design", response.Message);
}
[Fact]
public void ListTemplateNativeAlarmSources_ReturnsData()
{
_templateRepo.GetNativeAlarmSourcesByTemplateIdAsync(1, Arg.Any<CancellationToken>())
.Returns(new List<TemplateNativeAlarmSource>
{
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<ManagementSuccess>(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<ManagementSuccess>(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<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
Assert.Contains("Deployment", response.Message);
}
}