using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
using ZB.MOM.WW.OtOpcUa.Server.Phase7;
namespace ZB.MOM.WW.OtOpcUa.Server.Tests.Phase7;
///
/// PR B.4 — pins the precedence order Phase7Composer uses to pick an
/// :
/// driver-provided > DI-registered > none. Driver wins so a future
/// GalaxyDriver-as-IAlarmHistorianWriter takes the write path directly,
/// preserving the v1 invariant where a driver that natively owns the
/// historian client doesn't bounce through the sidecar IPC.
///
[Trait("Category", "Unit")]
public sealed class Phase7ComposerWriterSelectionTests
{
[Fact]
public async Task No_driver_no_injected_writer_returns_null()
{
await using var host = new DriverHost();
var writer = Phase7Composer.SelectAlarmHistorianWriter(host, injectedWriter: null, out var source);
writer.ShouldBeNull();
source.ShouldBeNull();
}
[Fact]
public async Task Injected_writer_only_is_selected()
{
await using var host = new DriverHost();
var injected = new RecordingWriter("from-di");
var writer = Phase7Composer.SelectAlarmHistorianWriter(host, injected, out var source);
writer.ShouldBeSameAs(injected);
source.ShouldStartWith("di:");
}
[Fact]
public async Task Driver_writer_wins_over_injected()
{
await using var host = new DriverHost();
var driver = new FakeDriverWithWriter("drv-1", "drv-out");
await host.RegisterAsync(driver, driverConfigJson: "{}", CancellationToken.None);
var injected = new RecordingWriter("from-di");
var writer = Phase7Composer.SelectAlarmHistorianWriter(host, injected, out var source);
writer.ShouldBeSameAs(driver);
source.ShouldBe("driver:drv-1");
}
[Fact]
public async Task First_driver_implementing_writer_wins()
{
await using var host = new DriverHost();
var driverNoWriter = new FakeDriverWithoutWriter("drv-1");
var driverWithWriter = new FakeDriverWithWriter("drv-2", "drv-out");
await host.RegisterAsync(driverNoWriter, "{}", CancellationToken.None);
await host.RegisterAsync(driverWithWriter, "{}", CancellationToken.None);
var writer = Phase7Composer.SelectAlarmHistorianWriter(host, injectedWriter: null, out var source);
writer.ShouldBeSameAs(driverWithWriter);
source.ShouldBe("driver:drv-2");
}
private sealed class RecordingWriter : IAlarmHistorianWriter
{
public string Tag { get; }
public RecordingWriter(string tag) { Tag = tag; }
public Task> WriteBatchAsync(
IReadOnlyList batch, CancellationToken cancellationToken)
{
var outcomes = new HistorianWriteOutcome[batch.Count];
for (var i = 0; i < outcomes.Length; i++) outcomes[i] = HistorianWriteOutcome.Ack;
return Task.FromResult>(outcomes);
}
}
private sealed class FakeDriverWithoutWriter : IDriver
{
public FakeDriverWithoutWriter(string id) { DriverInstanceId = id; }
public string DriverInstanceId { get; }
public string DriverType => "FakeNoWriter";
public Task InitializeAsync(string c, CancellationToken ct) => Task.CompletedTask;
public Task ReinitializeAsync(string c, CancellationToken ct) => Task.CompletedTask;
public Task ShutdownAsync(CancellationToken ct) => Task.CompletedTask;
public DriverHealth GetHealth() => new(DriverState.Healthy, DateTime.UtcNow, null);
public long GetMemoryFootprint() => 0;
public Task FlushOptionalCachesAsync(CancellationToken ct) => Task.CompletedTask;
}
private sealed class FakeDriverWithWriter : IDriver, IAlarmHistorianWriter
{
private readonly RecordingWriter _writer;
public FakeDriverWithWriter(string id, string tag)
{
DriverInstanceId = id;
_writer = new RecordingWriter(tag);
}
public string DriverInstanceId { get; }
public string DriverType => "FakeWithWriter";
public Task InitializeAsync(string c, CancellationToken ct) => Task.CompletedTask;
public Task ReinitializeAsync(string c, CancellationToken ct) => Task.CompletedTask;
public Task ShutdownAsync(CancellationToken ct) => Task.CompletedTask;
public DriverHealth GetHealth() => new(DriverState.Healthy, DateTime.UtcNow, null);
public long GetMemoryFootprint() => 0;
public Task FlushOptionalCachesAsync(CancellationToken ct) => Task.CompletedTask;
public Task> WriteBatchAsync(
IReadOnlyList batch, CancellationToken cancellationToken)
=> _writer.WriteBatchAsync(batch, cancellationToken);
}
}