using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.AbCip; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests; /// /// PR abcip-4.3 — end-to-end coverage that the synthetic _System folder + its five /// diagnostic variables ride a real ab_server lifecycle. Skipped when the binary isn't /// on PATH (). /// [Trait("Category", "Integration")] [Trait("Requires", "AbServer")] public sealed class AbCipSystemTagDiscoveryTests { [AbServerFact] public async Task System_folder_browses_and_each_variable_reads_non_empty() { var profile = KnownProfiles.ControlLogix; var fixture = new AbServerFixture(profile); await fixture.InitializeAsync(); try { var deviceUri = $"ab://127.0.0.1:{fixture.Port}/1,0"; var drv = new AbCipDriver(new AbCipDriverOptions { Devices = [new AbCipDeviceOptions(deviceUri, profile.Family)], Tags = [new AbCipTagDefinition("Counter", deviceUri, "TestDINT", AbCipDataType.DInt)], Timeout = TimeSpan.FromSeconds(5), }, "drv-system-tags"); await drv.InitializeAsync("{}", CancellationToken.None); // Discovery — five system variables exposed under _System/ for the device. var builder = new RecordingBuilder(); await drv.DiscoverAsync(builder, CancellationToken.None); builder.Folders.ShouldContain(f => f.BrowseName == "_System"); var systemVars = builder.Variables .Where(v => v.Info.FullName.StartsWith("_System/")) .Select(v => v.BrowseName) .ToList(); systemVars.ShouldContain("_ConnectionStatus"); systemVars.ShouldContain("_ScanRate"); systemVars.ShouldContain("_TagCount"); systemVars.ShouldContain("_DeviceError"); systemVars.ShouldContain("_LastScanTimeMs"); // Read — each system variable returns Good with a non-null value, with no // libplctag round-trip required. var refs = systemVars .Select(name => $"_System/{deviceUri}/{name}") .ToList(); var snaps = await drv.ReadAsync(refs, CancellationToken.None); for (var i = 0; i < snaps.Count; i++) { snaps[i].StatusCode.ShouldBe(AbCipStatusMapper.Good, $"system variable {refs[i]} should read Good"); snaps[i].Value.ShouldNotBeNull( $"system variable {refs[i]} should not be null"); } await drv.ShutdownAsync(CancellationToken.None); } finally { await fixture.DisposeAsync(); } } private sealed class RecordingBuilder : IAddressSpaceBuilder { public List<(string BrowseName, string DisplayName)> Folders { get; } = new(); public List<(string BrowseName, DriverAttributeInfo Info)> Variables { get; } = new(); public IAddressSpaceBuilder Folder(string browseName, string displayName) { Folders.Add((browseName, displayName)); return this; } public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo info) { Variables.Add((browseName, info)); return new Handle(info.FullName); } public void AddProperty(string _, DriverDataType __, object? ___) { } private sealed class Handle(string fullRef) : IVariableHandle { public string FullReference => fullRef; public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NullSink(); } private sealed class NullSink : IAlarmConditionSink { public void OnTransition(AlarmEventArgs args) { } } } }