using System; using System.Threading; using System.Threading.Tasks; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Galaxy; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.MxAccess; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Sta; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests { /// /// End-to-end smoke against the live MXAccess COM runtime + Galaxy ZB DB on this dev box. /// Skipped when ArchestrA bootstrap (aaBootstrap) isn't running. Verifies the /// ported can connect to LMXProxyServer, the /// can answer Discover against the live ZB schema, /// and a one-shot read returns a valid VTQ for the first deployed attribute it finds. /// [Trait("Category", "LiveMxAccess")] public sealed class MxAccessLiveSmokeTests { private static GalaxyRepositoryOptions DevZb() => new() { ConnectionString = "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;Connect Timeout=2;", CommandTimeoutSeconds = 10, }; private static async Task ArchestraReachableAsync() { try { var repo = new GalaxyRepository(DevZb()); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)); if (!await repo.TestConnectionAsync(cts.Token)) return false; using var sc = new System.ServiceProcess.ServiceController("aaBootstrap"); return sc.Status == System.ServiceProcess.ServiceControllerStatus.Running; } catch { return false; } } [Fact] public async Task Connect_to_local_LMXProxyServer_succeeds() { if (!await ArchestraReachableAsync()) return; using var pump = new StaPump("MxA-test-pump"); await pump.WaitForStartedAsync(); using var mx = new MxAccessClient(pump, new MxProxyAdapter(), "OtOpcUa-MxAccessSmoke"); var handle = await mx.ConnectAsync(); handle.ShouldBeGreaterThan(0); mx.IsConnected.ShouldBeTrue(); } [Fact] public async Task Backend_OpenSession_then_Discover_returns_objects_with_attributes() { if (!await ArchestraReachableAsync()) return; using var pump = new StaPump("MxA-test-pump"); await pump.WaitForStartedAsync(); using var mx = new MxAccessClient(pump, new MxProxyAdapter(), "OtOpcUa-MxAccessSmoke"); var backend = new MxAccessGalaxyBackend(new GalaxyRepository(DevZb()), mx); var session = await backend.OpenSessionAsync(new OpenSessionRequest { DriverInstanceId = "smoke" }, CancellationToken.None); session.Success.ShouldBeTrue(session.Error); var resp = await backend.DiscoverAsync(new DiscoverHierarchyRequest { SessionId = session.SessionId }, CancellationToken.None); resp.Success.ShouldBeTrue(resp.Error); resp.Objects.Length.ShouldBeGreaterThan(0); await backend.CloseSessionAsync(new CloseSessionRequest { SessionId = session.SessionId }, CancellationToken.None); } /// /// Live one-shot read against any attribute we discover. Best-effort — passes silently /// if no readable attribute is exposed (some Galaxy installs are configuration-only; /// we only assert the call shape is correct, not a specific value). /// [Fact] public async Task Backend_ReadValues_against_discovered_attribute_returns_a_response_shape() { if (!await ArchestraReachableAsync()) return; using var pump = new StaPump("MxA-test-pump"); await pump.WaitForStartedAsync(); using var mx = new MxAccessClient(pump, new MxProxyAdapter(), "OtOpcUa-MxAccessSmoke"); var backend = new MxAccessGalaxyBackend(new GalaxyRepository(DevZb()), mx); var session = await backend.OpenSessionAsync(new OpenSessionRequest { DriverInstanceId = "smoke" }, CancellationToken.None); var disc = await backend.DiscoverAsync(new DiscoverHierarchyRequest { SessionId = session.SessionId }, CancellationToken.None); var firstAttr = System.Linq.Enumerable.FirstOrDefault(disc.Objects, o => o.Attributes.Length > 0); if (firstAttr is null) { await backend.CloseSessionAsync(new CloseSessionRequest { SessionId = session.SessionId }, CancellationToken.None); return; } var fullRef = $"{firstAttr.TagName}.{firstAttr.Attributes[0].AttributeName}"; var read = await backend.ReadValuesAsync( new ReadValuesRequest { SessionId = session.SessionId, TagReferences = new[] { fullRef } }, CancellationToken.None); read.Success.ShouldBeTrue(); read.Values.Length.ShouldBe(1); // We don't assert the value (it may be Bad/Uncertain depending on what's running); // we only assert the response shape is correct end-to-end. read.Values[0].TagReference.ShouldBe(fullRef); await backend.CloseSessionAsync(new CloseSessionRequest { SessionId = session.SessionId }, CancellationToken.None); } } }