Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/AbCipRefreshTagDbTests.cs
2026-04-26 03:16:28 -04:00

92 lines
4.0 KiB
C#

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;
/// <summary>
/// PR abcip-4.4 — end-to-end coverage that writing <c>_RefreshTagDb</c> on the
/// synthetic system folder dispatches to <see cref="AbCipDriver.RebrowseAsync"/>
/// against a live <c>ab_server</c>. Mirrors <see cref="AbCipSystemTagDiscoveryTests"/>
/// but exercises the write entry point so the same outcome (template cache cleared,
/// enumerator re-walked) is observable through the OPC UA write surface.
/// </summary>
[Trait("Category", "Integration")]
[Trait("Requires", "AbServer")]
public sealed class AbCipRefreshTagDbTests
{
[AbServerFact]
public async Task RefreshTagDb_write_invokes_rebrowse_and_bumps_counter()
{
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)],
EnableControllerBrowse = true,
Timeout = TimeSpan.FromSeconds(5),
}, "drv-refresh-tagdb");
await drv.InitializeAsync("{}", CancellationToken.None);
// Discovery primes the cached builder so the subsequent _RefreshTagDb write
// has a target to dispatch to. The same fixture pattern from AbCipRebrowseTests
// is exercised here through the write surface instead of a direct
// RebrowseAsync call.
var builder = new RecordingBuilder();
await drv.DiscoverAsync(builder, CancellationToken.None);
// Seed the template cache so we can assert RebrowseAsync clears it — same
// behavioural contract as the unit test, validated against a live walker.
drv.TemplateCache.Put(deviceUri, 42, new AbCipUdtShape("T", 4, []));
drv.TemplateCache.Count.ShouldBe(1);
var refreshRef = $"_System/{deviceUri}/{AbCipSystemTagSource.RefreshTagDbName}";
var results = await drv.WriteAsync(
[new WriteRequest(refreshRef, true)], CancellationToken.None);
results[0].StatusCode.ShouldBe(AbCipStatusMapper.Good);
// RebrowseAsync drops the template cache + the diagnostics counter advances.
drv.TemplateCache.Count.ShouldBe(0);
drv.SystemTagSource.GetRefreshTriggerCount(deviceUri).ShouldBe(1);
drv.GetHealth().DiagnosticsOrEmpty["AbCip.RefreshTriggers"].ShouldBe(1);
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) { }
}
}
}