Merge b647-focas-alarms: FOCAS native alarms reach Part 9 conditions (ALARM: address) + respawn keeps acks host-side (#647)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -63,4 +63,37 @@ public sealed class FocasAddressBuilderTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Gitea #647: ALARM: selectors ----
|
||||
|
||||
[Theory]
|
||||
[InlineData("*", null, null, "ALARM:*")]
|
||||
[InlineData("*", 401, null, "ALARM:*")] // numbers apply to a type scope only
|
||||
[InlineData("OTHER", null, null, "ALARM:OTHER")]
|
||||
[InlineData("Servo", null, null, "ALARM:Servo")]
|
||||
[InlineData("Servo", 401, null, "ALARM:Servo/401")]
|
||||
[InlineData("Servo", 401, 453, "ALARM:Servo/401-453")]
|
||||
[InlineData("Servo", 401, 401, "ALARM:Servo/401")]
|
||||
[InlineData("T9", 750, 758, "ALARM:T9/750-758")]
|
||||
public void BuildAlarm_produces_a_selector_the_driver_can_parse(string scope, int? lo, int? hi, string expected)
|
||||
{
|
||||
var built = FocasAddressBuilder.BuildAlarm(scope, lo, hi);
|
||||
|
||||
built.ShouldBe(expected);
|
||||
FocasAddress.TryParse(built)!.Kind.ShouldBe(FocasAreaKind.Alarm);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Servo", 453, 401)] // reversed range
|
||||
[InlineData("Bogus", null, null)] // not a type
|
||||
[InlineData("", null, null)]
|
||||
public void BuildAlarm_returns_empty_rather_than_an_unusable_selector(string scope, int? lo, int? hi)
|
||||
=> FocasAddressBuilder.BuildAlarm(scope, lo, hi).ShouldBeEmpty();
|
||||
|
||||
[Fact]
|
||||
public void Every_offered_alarm_scope_composes_a_parseable_selector()
|
||||
{
|
||||
foreach (var scope in FocasAddressBuilder.AlarmScopes)
|
||||
FocasAddress.TryParse(FocasAddressBuilder.BuildAlarm(scope, 1, 2)).ShouldNotBeNull(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@ public sealed class FocasTagConfigModelTests
|
||||
[InlineData("PARAM:1815/0")]
|
||||
[InlineData("MACRO:500")]
|
||||
[InlineData("FIXED:Identity/SeriesNumber")]
|
||||
[InlineData("ALARM:*")]
|
||||
[InlineData("ALARM:OTHER")]
|
||||
[InlineData("ALARM:Servo/401-453")]
|
||||
[InlineData("ALARM:T9")]
|
||||
public void Validate_returns_null_for_every_address_form_the_driver_accepts(string address)
|
||||
=> new FocasTagConfigModel { Address = address }.Validate().ShouldBeNull();
|
||||
|
||||
@@ -104,6 +108,25 @@ public sealed class FocasTagConfigModelTests
|
||||
// The operator has to be able to act on it, so the message names the legal forms.
|
||||
error.ShouldContain("PARAM:");
|
||||
error.ShouldContain("FIXED:");
|
||||
error.ShouldContain("ALARM:");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #647 — a malformed ALARM: selector gets the selector's own reason, the same text the deploy-time
|
||||
/// inspector gives for the same string (one authority: <c>FocasAddress.DescribeAlarmAddressError</c>).
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("ALARM:Servo/10-5")]
|
||||
[InlineData("ALARM:Bogus")]
|
||||
[InlineData("ALARM:")]
|
||||
public void Validate_rejects_a_malformed_alarm_selector_with_the_gate_s_reason(string address)
|
||||
{
|
||||
var error = new FocasTagConfigModel { Address = address }.Validate();
|
||||
|
||||
error.ShouldNotBeNull();
|
||||
error.ShouldContain(FocasAddress.DescribeAlarmAddressError(address)!);
|
||||
FocasTagDefinitionFactory.Inspect("{\"address\":\"" + address + "\",\"alarm\":{}}")
|
||||
.ShouldContain(w => w.Contains(FocasAddress.DescribeAlarmAddressError(address)!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user