fix(native-alarms): a 4-column CSV import keeps stored MES overrides on the CLI path too (ScadaBridge#104)
The bulk SetInstanceNativeAlarmSourceOverridesCommand handler always wrote the entry's IsMesReportableOverride, so a CLI import of a legacy 4-column CSV (no MesReportable column) reset every imported source's MES override to inherit. The Central UI avoided it only by looking up the stored flag client-side. NativeAlarmSourceOverrideEntry gains an additive trailing KeepStoredMesReportable flag: when set, the server leaves an existing row's MES override untouched (a new row inherits) and ignores IsMesReportableOverride; unset keeps the old meaning (null = clear to inherit), so older senders are unaffected. The CLI and the Central UI now build their entries through one rule (ToOverrideEntry on each row type): no MesReportable column => KeepStoredMesReportable. CLI README and Component-CentralUI updated. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -4789,6 +4789,102 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
_templateRepo.ReceivedWithAnyArgs(1).SaveChangesAsync(default);
|
||||
}
|
||||
|
||||
private void StubFlattenedNativeSources(params string[] names)
|
||||
{
|
||||
var pipeline = Substitute.For<IFlatteningPipeline>();
|
||||
var flattened = new FlattenedConfiguration
|
||||
{
|
||||
NativeAlarmSources = names
|
||||
.Select(n => new ResolvedNativeAlarmSource { CanonicalName = n, IsLocked = false, Source = "Template" })
|
||||
.ToList()
|
||||
};
|
||||
pipeline.FlattenAndValidateAsync(1, Arg.Any<CancellationToken>(), Arg.Any<bool>())
|
||||
.Returns(Result<FlatteningPipelineResult>.Success(
|
||||
new FlatteningPipelineResult(flattened, "hash", ValidationResult.Success())));
|
||||
_services.AddScoped(_ => pipeline);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ScadaBridge#104: an entry that leaves the MES flag unspecified (a legacy 4-column CSV)
|
||||
/// keeps the stored MES override on an existing row while the retarget fields are applied.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SetInstanceNativeAlarmSourceOverrides_MesUnspecified_KeepsStoredMesOverride()
|
||||
{
|
||||
StubFlattenedNativeSources("Pressure");
|
||||
var existing = new InstanceNativeAlarmSourceOverride("Pressure")
|
||||
{
|
||||
InstanceId = 1, ConnectionNameOverride = "Old", IsMesReportableOverride = true
|
||||
};
|
||||
_templateRepo.GetNativeAlarmSourceOverrideAsync(1, "Pressure", Arg.Any<CancellationToken>())
|
||||
.Returns(existing);
|
||||
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(
|
||||
new SetInstanceNativeAlarmSourceOverridesCommand(1, new List<NativeAlarmSourceOverrideEntry>
|
||||
{
|
||||
new("Pressure", "Opc2", null, null, IsMesReportableOverride: null, KeepStoredMesReportable: true)
|
||||
}),
|
||||
"Deployer"));
|
||||
|
||||
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("Opc2", existing.ConnectionNameOverride);
|
||||
Assert.True(existing.IsMesReportableOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ScadaBridge#104 control: an entry that specifies the flag (a 5-column CSV with a blank
|
||||
/// MesReportable) still clears the stored MES override to inherit.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SetInstanceNativeAlarmSourceOverrides_MesSpecifiedNull_ClearsStoredMesOverride()
|
||||
{
|
||||
StubFlattenedNativeSources("Pressure");
|
||||
var existing = new InstanceNativeAlarmSourceOverride("Pressure")
|
||||
{
|
||||
InstanceId = 1, ConnectionNameOverride = "Old", IsMesReportableOverride = true
|
||||
};
|
||||
_templateRepo.GetNativeAlarmSourceOverrideAsync(1, "Pressure", Arg.Any<CancellationToken>())
|
||||
.Returns(existing);
|
||||
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(
|
||||
new SetInstanceNativeAlarmSourceOverridesCommand(1, new List<NativeAlarmSourceOverrideEntry>
|
||||
{
|
||||
new("Pressure", "Opc2", null, null, IsMesReportableOverride: null)
|
||||
}),
|
||||
"Deployer"));
|
||||
|
||||
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Null(existing.IsMesReportableOverride);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ScadaBridge#104: a new override row whose entry leaves the MES flag unspecified inherits
|
||||
/// it, even if the entry carries a stray value.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void SetInstanceNativeAlarmSourceOverrides_MesUnspecified_NewRowInherits()
|
||||
{
|
||||
StubFlattenedNativeSources("Pressure");
|
||||
InstanceNativeAlarmSourceOverride? added = null;
|
||||
_templateRepo.AddInstanceNativeAlarmSourceOverrideAsync(
|
||||
Arg.Do<InstanceNativeAlarmSourceOverride>(o => added = o), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var actor = CreateActor();
|
||||
actor.Tell(Envelope(
|
||||
new SetInstanceNativeAlarmSourceOverridesCommand(1, new List<NativeAlarmSourceOverrideEntry>
|
||||
{
|
||||
new("Pressure", "Opc2", null, null, IsMesReportableOverride: true, KeepStoredMesReportable: true)
|
||||
}),
|
||||
"Deployer"));
|
||||
|
||||
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.NotNull(added);
|
||||
Assert.Null(added!.IsMesReportableOverride);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that set instance native alarm source overrides, when one locked source, rejects whole batch.</summary>
|
||||
[Fact]
|
||||
public void SetInstanceNativeAlarmSourceOverrides_OneLockedSource_RejectsWholeBatch()
|
||||
|
||||
Reference in New Issue
Block a user