117 lines
5.5 KiB
C#
117 lines
5.5 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// End-to-end smoke against the live MXAccess COM runtime + Galaxy ZB DB on this dev box.
|
|
/// Skipped when ArchestrA bootstrap (<c>aaBootstrap</c>) isn't running. Verifies the
|
|
/// ported <see cref="MxAccessClient"/> can connect to <c>LMXProxyServer</c>, the
|
|
/// <see cref="MxAccessGalaxyBackend"/> can answer Discover against the live ZB schema,
|
|
/// and a one-shot read returns a valid VTQ for the first deployed attribute it finds.
|
|
/// </summary>
|
|
[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<bool> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
[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);
|
|
}
|
|
}
|
|
}
|