using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime; /// /// Tests for 's IReadable wiring. PR 4.2 ships the /// abstraction () and the wiring; PR 4.4 supplies the /// production gateway-backed reader. These tests verify the wiring against a fake /// reader plus the explicit "no reader → NotSupportedException" fallback that protects /// deployments running on this PR from silently producing wrong reads. /// public sealed class GalaxyDriverReadTests { private static GalaxyDriverOptions Opts() => new( new GalaxyGatewayOptions("https://mxgw.test:5001", "key"), new GalaxyMxAccessOptions("OtOpcUa-A"), new GalaxyRepositoryOptions(), new GalaxyReconnectOptions()); private sealed class FakeReader : IGalaxyDataReader { public IReadOnlyList? LastRequest { get; private set; } public Func, IReadOnlyList> Decide { get; set; } = tags => tags.Select(t => new DataValueSnapshot( Value: t, StatusCode: StatusCodeMap.Good, SourceTimestampUtc: DateTime.UtcNow, ServerTimestampUtc: DateTime.UtcNow)).ToArray(); public Task> ReadAsync( IReadOnlyList fullReferences, CancellationToken cancellationToken) { LastRequest = fullReferences; return Task.FromResult(Decide(fullReferences)); } } [Fact] public async Task ReadAsync_RoutesThroughInjectedReader() { var reader = new FakeReader(); var driver = new GalaxyDriver("g", Opts(), hierarchySource: null, dataReader: reader); var result = await driver.ReadAsync(["Tank1.Level", "Tank2.Level"], CancellationToken.None); reader.LastRequest.ShouldBe(new[] { "Tank1.Level", "Tank2.Level" }); result.Count.ShouldBe(2); result[0].Value.ShouldBe("Tank1.Level"); result[0].StatusCode.ShouldBe(StatusCodeMap.Good); } [Fact] public async Task ReadAsync_EmptyRequest_ReturnsEmpty_WithoutCallingReader() { var reader = new FakeReader(); var driver = new GalaxyDriver("g", Opts(), hierarchySource: null, dataReader: reader); var result = await driver.ReadAsync([], CancellationToken.None); result.ShouldBeEmpty(); reader.LastRequest.ShouldBeNull(); } [Fact] public async Task ReadAsync_NoReader_Throws_PointingAtPR44() { var driver = new GalaxyDriver("g", Opts()); var ex = await Should.ThrowAsync(() => driver.ReadAsync(["x"], CancellationToken.None)); ex.Message.ShouldContain("PR 4.4"); } [Fact] public async Task ReadAsync_AfterDispose_Throws() { var driver = new GalaxyDriver("g", Opts(), hierarchySource: null, dataReader: new FakeReader()); driver.Dispose(); await Should.ThrowAsync(() => driver.ReadAsync(["x"], CancellationToken.None)); } [Fact] public async Task ReadAsync_PreservesReaderStatusCodes() { var reader = new FakeReader { Decide = tags => new DataValueSnapshot[] { new(42.0, StatusCodeMap.Good, DateTime.UtcNow, DateTime.UtcNow), new(null, StatusCodeMap.BadNotConnected, null, DateTime.UtcNow), }, }; var driver = new GalaxyDriver("g", Opts(), hierarchySource: null, dataReader: reader); var result = await driver.ReadAsync(["a", "b"], CancellationToken.None); result[0].StatusCode.ShouldBe(StatusCodeMap.Good); result[1].StatusCode.ShouldBe(StatusCodeMap.BadNotConnected); } }