using System.Collections.Concurrent;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests;
///
/// PR 5.1 / #316 — end-to-end alarm-integration scaffold against a live TwinCAT 3 XAR
/// runtime. Skipped via when the VM isn't reachable.
/// Proves the driver's bridge surfaces TC3 EventLogger events
/// when the PLC's FB_AlarmHarness calls FB_TcLogEvent.
///
///
/// Required VM project state (see TwinCatProject/README.md
/// §"Alarm scenarios"):
///
/// - GVL GVL_Alarms with bTriggerEvent : BOOL + bAcked : BOOL.
/// A test harness flips bTriggerEvent from FALSE to TRUE via the
/// driver's WriteAsync path; FB_AlarmHarness sees the rising edge +
/// calls FB_TcLogEvent on the PLC side.
/// - FB FB_AlarmHarness wired into MAIN that calls FB_TcLogEvent
/// with a configured event class GUID + severity + source string when
/// GVL_Alarms.bTriggerEvent rises.
///
/// Decode caveat — Beckhoff doesn't ship a managed wrapper for
/// TcEventLogger in Beckhoff.TwinCAT.Ads v6, so the production
/// gate is best-effort and several event fields may surface as "Unknown".
/// This test asserts the bridge fires + the event has non-empty content; field-level
/// decode tightening lands on a follow-up PR (see
/// docs/v3/twincat-eventlogger-spike.md).
///
[Collection("TwinCATXar")]
[Trait("Category", "Integration")]
[Trait("Simulator", "TwinCAT-XAR")]
public sealed class TwinCATAlarmIntegrationTests(TwinCATXarFixture sim)
{
[TwinCATFact]
public async Task Driver_raises_alarm_event_when_PLC_logs_event()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
// Fixture-side state is documented in TwinCatProject/README.md §"Alarm scenarios".
// The harness is currently a build-only placeholder — once the GVL + FB_AlarmHarness
// ship, replace the Skip below with the live-trigger flow:
// 1. Init driver with EnableAlarms=true + GVL_Alarms.bTriggerEvent declared as a
// writable BOOL tag.
// 2. SubscribeAlarmsAsync([], ct).
// 3. WriteAsync to flip bTriggerEvent from FALSE to TRUE — the PLC's
// FB_AlarmHarness sees the rising edge + calls FB_TcLogEvent.
// 4. Assert OnAlarmEvent fires within ~5s with a non-empty Source + Message.
Assert.Skip(
"PR 5.1 / #316 — alarm-integration build-only scaffold. The GVL_Alarms + " +
"FB_AlarmHarness fixture hasn't been authored on the XAR project yet (build-time " +
"stubs ship under TwinCatProject/PLC; live trigger lands once the XAR project " +
"imports them). Until then this test self-skips even when the runtime is up.");
await Task.CompletedTask;
}
///
/// Build the AMS options with on so
/// the driver instantiates the alarm source. Mirrors the smoke-test option builder
/// but flips the alarm gate on; once the live fixture lands, the test body above
/// calls this helper directly.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Style", "IDE0051", Justification = "Used by the live test body once the fixture ships.")]
private static TwinCATDriverOptions BuildAlarmOptions(TwinCATXarFixture sim) => new()
{
Devices = [new TwinCATDeviceOptions(
HostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
DeviceName: $"xar-{sim.TargetNetId}:{sim.AmsPort}")],
Tags =
[
// bTriggerEvent rises FALSE→TRUE to fire the PLC-side LogEvent call.
new TwinCATTagDefinition(
Name: "AlarmTrigger",
DeviceHostAddress: $"ads://{sim.TargetNetId}:{sim.AmsPort}",
SymbolPath: "GVL_Alarms.bTriggerEvent",
DataType: TwinCATDataType.Bool,
Writable: true),
],
Probe = new TwinCATProbeOptions { Enabled = false },
EnableAlarms = true,
};
///
/// Helper kept for parity with the live test body once the fixture ships — collects
/// off the driver into a queue caller can
/// drain after the trigger flip.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Style", "IDE0051", Justification = "Used by the live test body once the fixture ships.")]
private static ConcurrentQueue WireAlarmCollector(IAlarmSource src)
{
var q = new ConcurrentQueue();
src.OnAlarmEvent += (_, e) => q.Enqueue(e);
return q;
}
}