test(dcl): OPC UA A&C live smoke (skippable) + test-infra A&C note
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Task 28: live smoke test for the OPC UA Alarms & Conditions adapter
|
||||
/// (<see cref="OpcUaDataConnection"/> as <c>IAlarmSubscribableConnection</c>).
|
||||
///
|
||||
/// Round-trips against an alarm-capable OPC UA endpoint
|
||||
/// (<c>opc.tcp://localhost:50000</c> — the infra OPC PLC server, see
|
||||
/// <c>infra/docker-compose.yml</c>). Marked <c>[SkippableFact]</c> so it reports
|
||||
/// Skipped — not failed — when the endpoint is unreachable OR does not expose
|
||||
/// Alarms & Conditions (no ConditionRefresh snapshot arrives). The OPC PLC
|
||||
/// simulator does not reliably expose A&C; see
|
||||
/// <c>docs/test_infra/test_infra.md</c> for the alarm-capable-server requirement.
|
||||
///
|
||||
/// The pure field→transition mapping is covered without a server by
|
||||
/// <see cref="Adapters.OpcUaAlarmMapperTests"/>; this test proves the live
|
||||
/// event-subscription + ConditionRefresh path end to end when infra supports it.
|
||||
/// </summary>
|
||||
[Trait("Category", "RequiresOpcUa")]
|
||||
public class OpcUaAlarmLiveSmokeTests
|
||||
{
|
||||
private const string EndpointUrl = "opc.tcp://localhost:50000";
|
||||
|
||||
[SkippableFact]
|
||||
public async Task SubscribeAlarms_DeliversConditionRefreshSnapshot()
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(_ => { });
|
||||
var clientFactory = new RealOpcUaClientFactory(new OpcUaGlobalOptions(), loggerFactory);
|
||||
var adapter = new OpcUaDataConnection(clientFactory, NullLogger<OpcUaDataConnection>.Instance);
|
||||
|
||||
// Probe the endpoint. An unreachable infra server surfaces a socket/timeout
|
||||
// error from deep in the OPC Foundation SDK — treat as "infra not available".
|
||||
try
|
||||
{
|
||||
await adapter.ConnectAsync(new Dictionary<string, string> { ["EndpointUrl"] = EndpointUrl });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Skip.If(true, $"OPC UA test server not reachable on {EndpointUrl}: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var snapshotComplete = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
void OnTransition(NativeAlarmTransition t)
|
||||
{
|
||||
if (t.Kind == AlarmTransitionKind.SnapshotComplete)
|
||||
{
|
||||
snapshotComplete.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Empty source reference = mirror every condition under the server's
|
||||
// event notifier; ConditionRefresh replays the active set then a
|
||||
// SnapshotComplete sentinel.
|
||||
await adapter.SubscribeAlarmsAsync(sourceReference: string.Empty, conditionFilter: null, OnTransition);
|
||||
|
||||
var done = await Task.WhenAny(snapshotComplete.Task, Task.Delay(TimeSpan.FromSeconds(10)));
|
||||
|
||||
// Reachable but no A&C snapshot within the window → the server does not
|
||||
// expose Alarms & Conditions. Skip rather than fail.
|
||||
Skip.IfNot(done == snapshotComplete.Task,
|
||||
$"OPC UA endpoint {EndpointUrl} reachable but delivered no A&C ConditionRefresh snapshot — " +
|
||||
"server likely does not expose Alarms & Conditions.");
|
||||
|
||||
Assert.True(snapshotComplete.Task.IsCompletedSuccessfully);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await adapter.DisconnectAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user