feat(GWC-37,WRK-32): refresh live alarm watch lists
This commit is contained in:
@@ -287,8 +287,12 @@ public sealed class AlarmCommandHandlerTests
|
||||
handler.PollOnce();
|
||||
Assert.Equal(5, guardInvocations);
|
||||
|
||||
handler.Unsubscribe();
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => handler.UpdateWatchList(Array.Empty<AlarmSubtagTarget>()));
|
||||
Assert.Equal(6, guardInvocations);
|
||||
|
||||
handler.Unsubscribe();
|
||||
Assert.Equal(7, guardInvocations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -324,6 +328,8 @@ public sealed class AlarmCommandHandlerTests
|
||||
() => handler.AcknowledgeByName("", "", "", "", "", "", "", ""));
|
||||
Assert.Throws<InvalidOperationException>(() => handler.QueryActive(null, out _));
|
||||
Assert.Throws<InvalidOperationException>(() => handler.PollOnce());
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => handler.UpdateWatchList(Array.Empty<AlarmSubtagTarget>()));
|
||||
Assert.Throws<InvalidOperationException>(() => handler.Unsubscribe());
|
||||
}
|
||||
|
||||
@@ -412,6 +418,114 @@ public sealed class AlarmCommandHandlerTests
|
||||
Assert.Equal(AlarmProviderMode.Subtag, evt.OnAlarmProviderModeChanged.Mode);
|
||||
}
|
||||
|
||||
/// <summary>An explicitly fallback-capable auto subscription builds a replaceable standby even when discovery is initially empty.</summary>
|
||||
[Fact]
|
||||
public void Subscribe_AutoModeWithEmptyWatchListAndFallbackEnabled_BuildsReplaceableStandby()
|
||||
{
|
||||
FakeConsumer primary = new FakeConsumer { AcknowledgeReturn = 11 };
|
||||
FakeConsumer initialStandby = new FakeConsumer { AcknowledgeReturn = 22 };
|
||||
FakeConsumer replacement = new FakeConsumer { AcknowledgeReturn = 33 };
|
||||
int buildCount = 0;
|
||||
AlarmCommandHandler handler = new AlarmCommandHandler(
|
||||
new MxAccessEventQueue(),
|
||||
() => primary,
|
||||
threadAffinityCheck: null,
|
||||
comFactory: null,
|
||||
standbyFactory: _ => ++buildCount == 1 ? initialStandby : replacement);
|
||||
|
||||
handler.Subscribe(new SubscribeAlarmsCommand
|
||||
{
|
||||
SubscriptionExpression = @"\\HOST\Galaxy!Area",
|
||||
FallbackEnabled = true,
|
||||
}, "s1");
|
||||
handler.UpdateWatchList(new[] { NewTarget("Galaxy!Area.Tank01.Alarm") });
|
||||
|
||||
Assert.Equal(2, buildCount);
|
||||
Assert.Equal(@"\\HOST\Galaxy!Area", replacement.LastSubscription);
|
||||
Assert.True(initialStandby.Disposed);
|
||||
Assert.Equal(11, handler.Acknowledge(Guid.Empty, "", "", "", "", ""));
|
||||
}
|
||||
|
||||
/// <summary>A replacement is rejected when no fallback-capable subscription is active.</summary>
|
||||
[Fact]
|
||||
public void UpdateWatchList_WithoutFallbackCapableSubscription_Throws()
|
||||
{
|
||||
AlarmCommandHandler handler = new AlarmCommandHandler(
|
||||
new MxAccessEventQueue(),
|
||||
() => new FakeConsumer());
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => handler.UpdateWatchList(new[] { NewTarget("Galaxy!Area.Tank01.Alarm") }));
|
||||
|
||||
handler.Subscribe(new SubscribeAlarmsCommand
|
||||
{
|
||||
SubscriptionExpression = @"\\HOST\Galaxy!Area",
|
||||
ForcedMode = AlarmProviderMode.Alarmmgr,
|
||||
}, "s1");
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => handler.UpdateWatchList(new[] { NewTarget("Galaxy!Area.Tank02.Alarm") }));
|
||||
}
|
||||
|
||||
/// <summary>A replacement setup failure leaves the old standby and primary mode intact.</summary>
|
||||
[Fact]
|
||||
public void UpdateWatchList_WhenReplacementSubscribeFails_PreservesOldStandbyAndPrimaryMode()
|
||||
{
|
||||
FakeConsumer primary = new FakeConsumer { AcknowledgeReturn = 11 };
|
||||
FakeConsumer oldStandby = new FakeConsumer { AcknowledgeReturn = 22 };
|
||||
FakeConsumer failedReplacement = new FakeConsumer { ThrowOnSubscribe = true, AcknowledgeReturn = 33 };
|
||||
int buildCount = 0;
|
||||
AlarmCommandHandler handler = new AlarmCommandHandler(
|
||||
new MxAccessEventQueue(),
|
||||
() => primary,
|
||||
null,
|
||||
null,
|
||||
_ => ++buildCount == 1 ? oldStandby : failedReplacement);
|
||||
SubscribeAlarmsCommand subscribe = new SubscribeAlarmsCommand
|
||||
{
|
||||
SubscriptionExpression = @"\\HOST\Galaxy!Area",
|
||||
FallbackEnabled = true,
|
||||
};
|
||||
subscribe.WatchList.Add(NewTarget("Galaxy!Area.Tank01.Alarm"));
|
||||
handler.Subscribe(subscribe, "s1");
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => handler.UpdateWatchList(new[] { NewTarget("Galaxy!Area.Tank02.Alarm") }));
|
||||
|
||||
Assert.False(oldStandby.Disposed);
|
||||
Assert.True(failedReplacement.Disposed);
|
||||
Assert.Equal(11, handler.Acknowledge(Guid.Empty, "", "", "", "", ""));
|
||||
}
|
||||
|
||||
/// <summary>A successful replacement while failed over keeps subtag mode active and delegates to the new standby.</summary>
|
||||
[Fact]
|
||||
public void UpdateWatchList_WhileSubtagActive_PreservesModeAndUsesReplacement()
|
||||
{
|
||||
FakeConsumer primary = new FakeConsumer { ThrowOnSubscribe = true, AcknowledgeReturn = 11 };
|
||||
FakeConsumer oldStandby = new FakeConsumer { AcknowledgeReturn = 22 };
|
||||
FakeConsumer replacement = new FakeConsumer { AcknowledgeReturn = 33 };
|
||||
int buildCount = 0;
|
||||
AlarmCommandHandler handler = new AlarmCommandHandler(
|
||||
new MxAccessEventQueue(),
|
||||
() => primary,
|
||||
null,
|
||||
null,
|
||||
_ => ++buildCount == 1 ? oldStandby : replacement);
|
||||
SubscribeAlarmsCommand subscribe = new SubscribeAlarmsCommand
|
||||
{
|
||||
SubscriptionExpression = @"\\HOST\Galaxy!Area",
|
||||
FallbackEnabled = true,
|
||||
Failover = new AlarmFailoverConfig { ConsecutiveFailureThreshold = 1 },
|
||||
};
|
||||
subscribe.WatchList.Add(NewTarget("Galaxy!Area.Tank01.Alarm"));
|
||||
handler.Subscribe(subscribe, "s1");
|
||||
|
||||
handler.UpdateWatchList(new[] { NewTarget("Galaxy!Area.Tank02.Alarm") });
|
||||
|
||||
Assert.True(oldStandby.Disposed);
|
||||
Assert.Equal(33, handler.Acknowledge(Guid.Empty, "", "", "", "", ""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A non-failover subscribe (alarmmgr-only) never enqueues a
|
||||
/// provider-mode-changed event, and a subsequent Unsubscribe detaches
|
||||
@@ -467,6 +581,14 @@ public sealed class AlarmCommandHandlerTests
|
||||
};
|
||||
}
|
||||
|
||||
private static AlarmSubtagTarget NewTarget(string alarmFullReference) => new AlarmSubtagTarget
|
||||
{
|
||||
AlarmFullReference = alarmFullReference,
|
||||
ActiveSubtag = alarmFullReference + ".InAlarm",
|
||||
AckedSubtag = alarmFullReference + ".Acked",
|
||||
AckCommentSubtag = alarmFullReference + ".AckMsg",
|
||||
};
|
||||
|
||||
private sealed class FakeConsumer : IMxAccessAlarmConsumer
|
||||
{
|
||||
#pragma warning disable CS0067 // Event never invoked — fake; AlarmCommandHandler tests don't drive transitions.
|
||||
|
||||
Reference in New Issue
Block a user