feat(adminui): Transport dropdown on AbCip/AbLegacy driver forms (live-verified on docker-dev)
- AbCip/AbLegacy driver forms gain a Transport InputSelect; FromOptions/ToOptions now carry Transport, so saving any other field no longer silently reverts Native to libplctag. - Both drivers log the constructed transport once per init. - AbCip factory now takes the host ILoggerFactory (it ran on NullLogger in production, hiding every AbCip log line); DriverFactoryBootstrap passes it. - Gate results: AdminUI /run (T19) native, inverted (dead port) and libplctag legs.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The transport the instance runs on is logged at every init through the HOST-supplied logger factory,
|
||||
/// so an operator can tell from the node log which stack serves a driver (T19 live gate). The factory
|
||||
/// path is the one under test: a driver built without a logger writes to the null logger.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class AbCipTransportInitLogTests
|
||||
{
|
||||
private static string Config(string transport) =>
|
||||
$$"""{"transport":"{{transport}}","probe":{"enabled":false},"devices":[{"hostAddress":"ab://10.0.0.1:44818/1,0","plcFamily":"ControlLogix"}]}""";
|
||||
|
||||
[Theory]
|
||||
[InlineData("Native", AbCipTransport.Native)]
|
||||
[InlineData("Libplctag", AbCipTransport.Libplctag)]
|
||||
public async Task Init_logs_the_transport_through_the_registered_logger_factory(string json, AbCipTransport expected)
|
||||
{
|
||||
var captured = new List<string>();
|
||||
using var factory = new CapturingLoggerFactory(captured);
|
||||
var driver = AbCipDriverFactoryExtensions.CreateInstance("drv-t19", Config(json), factory);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync(Config(json), TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await driver.ShutdownAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
captured.ShouldContain(m => m.Contains("drv-t19") && m.Contains($"transport={expected}"));
|
||||
}
|
||||
|
||||
private sealed class CapturingLoggerFactory(List<string> sink) : ILoggerFactory
|
||||
{
|
||||
public ILogger CreateLogger(string categoryName) => new CapturingLogger(sink);
|
||||
public void AddProvider(ILoggerProvider provider) { }
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
private sealed class CapturingLogger(List<string> sink) : ILogger
|
||||
{
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
lock (sink) sink.Add(formatter(state, exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The transport the instance runs on is logged at every init through the HOST-supplied logger factory,
|
||||
/// so an operator can tell from the node log which stack serves a driver (T19 live gate). The factory
|
||||
/// path is the one under test: a driver built without a logger writes to the null logger.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class AbLegacyTransportInitLogTests
|
||||
{
|
||||
private static string Config(string transport) =>
|
||||
$$"""{"transport":"{{transport}}","probe":{"enabled":false},"devices":[{"hostAddress":"ab://10.0.0.1:44818/1,0","plcFamily":"Slc500"}]}""";
|
||||
|
||||
[Theory]
|
||||
[InlineData("Native", AbLegacyTransport.Native)]
|
||||
[InlineData("Libplctag", AbLegacyTransport.Libplctag)]
|
||||
public async Task Init_logs_the_transport_through_the_registered_logger_factory(string json, AbLegacyTransport expected)
|
||||
{
|
||||
var captured = new List<string>();
|
||||
using var factory = new CapturingLoggerFactory(captured);
|
||||
var driver = AbLegacyDriverFactoryExtensions.CreateInstance("drv-t19", Config(json), factory);
|
||||
try
|
||||
{
|
||||
await driver.InitializeAsync(Config(json), TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await driver.ShutdownAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
captured.ShouldContain(m => m.Contains("drv-t19") && m.Contains($"transport={expected}"));
|
||||
}
|
||||
|
||||
private sealed class CapturingLoggerFactory(List<string> sink) : ILoggerFactory
|
||||
{
|
||||
public ILogger CreateLogger(string categoryName) => new CapturingLogger(sink);
|
||||
public void AddProvider(ILoggerProvider provider) { }
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
private sealed class CapturingLogger(List<string> sink) : ILogger
|
||||
{
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
lock (sink) sink.Add(formatter(state, exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user