fix(management): case-variant instance override names are rejected with a did-you-mean hint (ScadaBridge#105)

Verified the ManagementActor bulk attribute override and single/bulk native alarm source
override paths match names ordinally and already reject a case-variant name (nothing stored);
the error now names the single case-insensitive match. Tests pin the rejection.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 16:23:37 -05:00
parent 0c2e852a62
commit a2c3a2d2b4
2 changed files with 71 additions and 4 deletions
@@ -2811,6 +2811,58 @@ public class ManagementActorTests : TestKit, IDisposable
// command up front WITHOUT persisting any of the overrides.
// ========================================================================
/// <summary>ScadaBridge#105: a bulk attribute override differing from the member only by case is rejected with a hint, nothing stored.</summary>
[Fact]
public void SetInstanceOverrides_CaseVariantName_IsRejectedWithHint_PersistsNothing()
{
var instance = new Instance("Pump5") { Id = 5, SiteId = 1, TemplateId = 1 };
_templateRepo.GetInstanceByIdAsync(5, Arg.Any<CancellationToken>()).Returns(instance);
_templateRepo.GetAttributesByTemplateIdAsync(1, Arg.Any<CancellationToken>())
.Returns(new List<TemplateAttribute> { new("Setpoint") { Id = 1, TemplateId = 1 } });
_templateRepo.GetOverridesByInstanceIdAsync(5, Arg.Any<CancellationToken>())
.Returns(new List<Commons.Entities.Instances.InstanceAttributeOverride>());
var actor = CreateActor();
actor.Tell(Envelope(new SetInstanceOverridesCommand(5, new Dictionary<string, string?> { ["setpoint"] = "1" }), "Deployer"));
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
Assert.Contains("did you mean 'Setpoint'", response.Error);
_templateRepo.DidNotReceiveWithAnyArgs().AddInstanceAttributeOverrideAsync(default!, default);
}
/// <summary>ScadaBridge#105: a bulk native-source override differing only by case is rejected, nothing stored.</summary>
[Fact]
public void SetInstanceNativeAlarmSourceOverrides_CaseVariantName_IsRejectedWithHint()
{
StubFlattenedNativeSources("Pressure");
var actor = CreateActor();
actor.Tell(Envelope(
new SetInstanceNativeAlarmSourceOverridesCommand(1, new List<NativeAlarmSourceOverrideEntry>
{
new("pressure", "Opc2", null, null)
}),
"Deployer"));
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
Assert.Contains("did you mean 'Pressure'", response.Error);
_templateRepo.DidNotReceiveWithAnyArgs().AddInstanceNativeAlarmSourceOverrideAsync(default!, default);
}
/// <summary>ScadaBridge#105: a single native-source override differing only by case is rejected, nothing stored.</summary>
[Fact]
public void SetInstanceNativeAlarmSourceOverride_CaseVariantName_IsRejectedWithHint()
{
StubFlattenedNativeSources("Pressure");
var actor = CreateActor();
actor.Tell(Envelope(
new SetInstanceNativeAlarmSourceOverrideCommand(1, "PRESSURE", "Opc2", null, null), "Deployer"));
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
Assert.Contains("did you mean 'Pressure'", response.Error);
_templateRepo.DidNotReceiveWithAnyArgs().AddInstanceNativeAlarmSourceOverrideAsync(default!, default);
}
/// <summary>Verifies that set instance overrides, when with one invalid attribute, persists no overrides.</summary>
[Fact]
public void SetInstanceOverrides_WithOneInvalidAttribute_PersistsNoOverrides()