chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the Rider Solution Explorer mirrors the module structure. Folders: Core, Server, Drivers (with a nested Driver CLIs subfolder), Client, Tooling. - Move every project folder on disk with git mv (history preserved as renames). - Recompute relative paths in 57 .csproj files: cross-category ProjectReferences, the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external mxaccessgw refs in Driver.Galaxy and its test project. - Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders. - Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL, integration, install). Build green (0 errors); unit tests pass. Docs left for a separate pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
using Serilog;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ScriptedAlarmSourceTests
|
||||
{
|
||||
private static async Task<(ScriptedAlarmEngine e, ScriptedAlarmSource s, FakeUpstream u)> BuildAsync()
|
||||
{
|
||||
var up = new FakeUpstream();
|
||||
up.Set("Temp", 50);
|
||||
var logger = new LoggerConfiguration().CreateLogger();
|
||||
var engine = new ScriptedAlarmEngine(up, new InMemoryAlarmStateStore(),
|
||||
new ScriptLoggerFactory(logger), logger);
|
||||
await engine.LoadAsync([
|
||||
new ScriptedAlarmDefinition(
|
||||
"Plant/Line1::HighTemp",
|
||||
"Plant/Line1",
|
||||
"HighTemp",
|
||||
AlarmKind.LimitAlarm,
|
||||
AlarmSeverity.High,
|
||||
"Temp {Temp}C",
|
||||
"""return (int)ctx.GetTag("Temp").Value > 100;"""),
|
||||
new ScriptedAlarmDefinition(
|
||||
"Plant/Line2::OtherAlarm",
|
||||
"Plant/Line2",
|
||||
"OtherAlarm",
|
||||
AlarmKind.AlarmCondition,
|
||||
AlarmSeverity.Low,
|
||||
"other",
|
||||
"""return false;"""),
|
||||
], CancellationToken.None);
|
||||
|
||||
var source = new ScriptedAlarmSource(engine);
|
||||
return (engine, source, up);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Subscribe_with_empty_filter_receives_every_alarm_emission()
|
||||
{
|
||||
var (engine, source, up) = await BuildAsync();
|
||||
using var _e = engine;
|
||||
using var _s = source;
|
||||
|
||||
var events = new List<AlarmEventArgs>();
|
||||
source.OnAlarmEvent += (_, e) => events.Add(e);
|
||||
var handle = await source.SubscribeAlarmsAsync([], TestContext.Current.CancellationToken);
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
|
||||
events.Count.ShouldBe(1);
|
||||
events[0].ConditionId.ShouldBe("Plant/Line1::HighTemp");
|
||||
events[0].SourceNodeId.ShouldBe("Plant/Line1");
|
||||
events[0].Severity.ShouldBe(AlarmSeverity.High);
|
||||
events[0].AlarmType.ShouldBe("LimitAlarm");
|
||||
events[0].Message.ShouldBe("Temp 150C");
|
||||
|
||||
await source.UnsubscribeAlarmsAsync(handle, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Subscribe_with_equipment_prefix_filters_by_that_prefix()
|
||||
{
|
||||
var (engine, source, up) = await BuildAsync();
|
||||
using var _e = engine;
|
||||
using var _s = source;
|
||||
|
||||
var events = new List<AlarmEventArgs>();
|
||||
source.OnAlarmEvent += (_, e) => events.Add(e);
|
||||
|
||||
// Subscribe only to Line1 alarms.
|
||||
var handle = await source.SubscribeAlarmsAsync(["Plant/Line1"], TestContext.Current.CancellationToken);
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
|
||||
events.Count.ShouldBe(1);
|
||||
events[0].SourceNodeId.ShouldBe("Plant/Line1");
|
||||
|
||||
await source.UnsubscribeAlarmsAsync(handle, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Unsubscribe_stops_further_events()
|
||||
{
|
||||
var (engine, source, up) = await BuildAsync();
|
||||
using var _e = engine;
|
||||
using var _s = source;
|
||||
|
||||
var events = new List<AlarmEventArgs>();
|
||||
source.OnAlarmEvent += (_, e) => events.Add(e);
|
||||
var handle = await source.SubscribeAlarmsAsync([], TestContext.Current.CancellationToken);
|
||||
await source.UnsubscribeAlarmsAsync(handle, TestContext.Current.CancellationToken);
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
|
||||
events.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AcknowledgeAsync_routes_to_engine_with_default_user()
|
||||
{
|
||||
var (engine, source, up) = await BuildAsync();
|
||||
using var _e = engine;
|
||||
using var _s = source;
|
||||
|
||||
up.Push("Temp", 150);
|
||||
await Task.Delay(200);
|
||||
engine.GetState("Plant/Line1::HighTemp")!.Acked.ShouldBe(AlarmAckedState.Unacknowledged);
|
||||
|
||||
await source.AcknowledgeAsync([new AlarmAcknowledgeRequest(
|
||||
"Plant/Line1", "Plant/Line1::HighTemp", "ack via opcua")],
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
var state = engine.GetState("Plant/Line1::HighTemp")!;
|
||||
state.Acked.ShouldBe(AlarmAckedState.Acknowledged);
|
||||
state.LastAckUser.ShouldBe("opcua-client");
|
||||
state.LastAckComment.ShouldBe("ack via opcua");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Null_arguments_rejected()
|
||||
{
|
||||
var (engine, source, _) = await BuildAsync();
|
||||
using var _e = engine;
|
||||
using var _s = source;
|
||||
|
||||
await Should.ThrowAsync<ArgumentNullException>(async () =>
|
||||
await source.SubscribeAlarmsAsync(null!, TestContext.Current.CancellationToken));
|
||||
await Should.ThrowAsync<ArgumentNullException>(async () =>
|
||||
await source.UnsubscribeAlarmsAsync(null!, TestContext.Current.CancellationToken));
|
||||
await Should.ThrowAsync<ArgumentNullException>(async () =>
|
||||
await source.AcknowledgeAsync(null!, TestContext.Current.CancellationToken));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user