Add runtime address space rebuild integration tests

Tests verify nodes can be added/removed from the OPC UA server at
runtime by mutating FakeGalaxyRepository and triggering a rebuild.
Uses real OPC UA client sessions to browse, subscribe, and observe
changes.

Tests cover:
- Browse initial hierarchy via OPC UA client
- Add object at runtime → new node appears on browse
- Remove object → node disappears from browse
- Subscribe to node, then remove it → publishes Bad quality
- Surviving nodes still browsable after partial rebuild
- Add/remove individual attributes at runtime

Infrastructure:
- OpcUaTestClient helper for programmatic OPC UA client connections
- OpcUaServerFixture updated with GalaxyRepository/MxProxy accessors
- OpcUaService.TriggerRebuild() exposed for test-driven rebuilds
- Namespace index resolved dynamically via session namespace table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-25 06:32:31 -04:00
parent 44177acf64
commit e4aaee10f7
5 changed files with 448 additions and 3 deletions

View File

@@ -25,15 +25,37 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
public int OpcUaPort { get; }
public string EndpointUrl => $"opc.tcp://localhost:{OpcUaPort}/LmxOpcUa";
/// <summary>
/// The fake Galaxy repository injected into the service. Mutate Hierarchy/Attributes
/// then call Service.TriggerRebuild() to simulate a Galaxy redeployment.
/// </summary>
public FakeGalaxyRepository? GalaxyRepository { get; }
/// <summary>
/// The fake MxAccess client injected into the service (when using WithFakeMxAccessClient).
/// </summary>
public FakeMxAccessClient? MxAccessClient { get; }
/// <summary>
/// The fake MxProxy injected into the service (when using WithFakes).
/// </summary>
public FakeMxProxy? MxProxy { get; }
private readonly OpcUaServiceBuilder _builder;
private bool _started;
public OpcUaServerFixture(OpcUaServiceBuilder builder)
private OpcUaServerFixture(OpcUaServiceBuilder builder,
FakeGalaxyRepository? repo = null,
FakeMxAccessClient? mxClient = null,
FakeMxProxy? mxProxy = null)
{
OpcUaPort = Interlocked.Increment(ref _nextPort);
_builder = builder;
_builder.WithOpcUaPort(OpcUaPort);
_builder.DisableDashboard();
GalaxyRepository = repo;
MxAccessClient = mxClient;
MxProxy = mxProxy;
}
/// <summary>
@@ -56,7 +78,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
.WithGalaxyRepository(r)
.WithGalaxyName("TestGalaxy");
return new OpcUaServerFixture(builder);
return new OpcUaServerFixture(builder, repo: r, mxProxy: p);
}
/// <summary>
@@ -79,7 +101,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
.WithGalaxyRepository(r)
.WithGalaxyName("TestGalaxy");
return new OpcUaServerFixture(builder);
return new OpcUaServerFixture(builder, repo: r, mxClient: client);
}
public Task InitializeAsync()