ed0881d672
Task 22 (spec section 8 P5), with the controller rulings that exclude PLC-5 and LogixPccc. - Transport is nullable on both option records; null (key absent) means "use the default" and is never serialized. Factory DTO parse: absent -> null; a bad value still fails via ParseEnum. - One resolver per driver (AbCipTransportResolver / AbLegacyTransportResolver), used by the driver constructor, the Test Connect probe and (through the driver) the CLI. AbCip: null -> Native. AbLegacy: null -> Native unless any device is Plc5 or LogixPccc, then the whole driver resolves to Libplctag. Explicit values always win; explicit Native with a Plc5/LogixPccc device logs one Warning at init. - Init log shows configured vs effective, e.g. transport=Native (default). - AdminUI forms: "Default (...)" / "Native (managed)" / "libplctag (fallback)". Default omits the key on save (reverses Task 19's explicit re-emit, which would have pinned every edited driver). - CLIs: --transport default|native|libplctag, default "default" via the resolver, so --plc-type Plc5 with no --transport runs libplctag. - Tests pinned to Libplctag where a default-path runtime was created: found by instrumenting the native factories with the current xUnit test name (one unit test, AbLegacyReinitConfigAdoption; plus the two fixture-gated AbCip probe E2E tests whose exact messages were proven on libplctag). TransportMatrix and the differential harness already pass explicit transports. - Switch/resolver/CLI/form tests are falsified by mutating the resolver (default -> Libplctag, exclusion removed, AbLegacy default -> Libplctag): each mutation turns them red. - Replaces the inline role-name literal in AbCipDriverFactoryExtensions' ParseEnum call with a named constant; RoleAuthorityGuardTests was already red on the base commit for that line. - Docs: AbCip.md / AbLegacy.md default + release note; CLAUDE.md AB section. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
58 lines
2.4 KiB
C#
58 lines
2.4 KiB
C#
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 is null ? "" : $"\"transport\":\"{transport}\",")}}"probe":{"enabled":false},"devices":[{"hostAddress":"ab://10.0.0.1:44818/1,0","plcFamily":"ControlLogix"}]}""";
|
|
|
|
/// <summary>The init line names the configured-vs-effective transport (Task 22).</summary>
|
|
[Theory]
|
|
[InlineData("Native", "Native (configured)")]
|
|
[InlineData("Libplctag", "Libplctag (configured)")]
|
|
[InlineData(null, "Native (default)")]
|
|
public async Task Init_logs_the_transport_through_the_registered_logger_factory(string? json, string 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));
|
|
}
|
|
}
|
|
}
|