Add integration test harness: OpcUaServiceBuilder + OpcUaServerFixture
OpcUaServiceBuilder provides fluent API for constructing OpcUaService with dependency overrides (IMxProxy, IGalaxyRepository, IMxAccessClient). WithMxAccessClient skips the STA thread and COM interop entirely. OpcUaServerFixture wraps the service lifecycle with automatic port allocation (atomic counter starting at 16000), guaranteed cleanup via IAsyncLifetime, and factory methods for common test scenarios: - WithFakes() — FakeMxProxy + FakeGalaxyRepository with standard data - WithFakeMxAccessClient() — bypasses COM, fastest for most tests Also adds TestData helper with reusable hierarchy/attributes matching gr/layout.md, and 5 fixture tests verifying startup, shutdown, port isolation, and address space building. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
123
src/ZB.MOM.WW.LmxOpcUa.Host/OpcUaServiceBuilder.cs
Normal file
123
src/ZB.MOM.WW.LmxOpcUa.Host/OpcUaServiceBuilder.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
|
||||
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
|
||||
|
||||
namespace ZB.MOM.WW.LmxOpcUa.Host
|
||||
{
|
||||
/// <summary>
|
||||
/// Fluent builder for constructing OpcUaService with dependency overrides.
|
||||
/// Used by integration tests to substitute fakes for COM/DB components.
|
||||
/// </summary>
|
||||
internal class OpcUaServiceBuilder
|
||||
{
|
||||
private AppConfiguration _config = new AppConfiguration();
|
||||
private IMxProxy? _mxProxy;
|
||||
private IGalaxyRepository? _galaxyRepository;
|
||||
private IMxAccessClient? _mxAccessClient;
|
||||
private bool _mxProxySet;
|
||||
private bool _galaxyRepositorySet;
|
||||
private bool _mxAccessClientSet;
|
||||
|
||||
public OpcUaServiceBuilder WithConfig(AppConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaServiceBuilder WithOpcUaPort(int port)
|
||||
{
|
||||
_config.OpcUa.Port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaServiceBuilder WithGalaxyName(string name)
|
||||
{
|
||||
_config.OpcUa.GalaxyName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaServiceBuilder WithMxProxy(IMxProxy? proxy)
|
||||
{
|
||||
_mxProxy = proxy;
|
||||
_mxProxySet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaServiceBuilder WithGalaxyRepository(IGalaxyRepository? repository)
|
||||
{
|
||||
_galaxyRepository = repository;
|
||||
_galaxyRepositorySet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override the MxAccessClient directly, skipping STA thread and COM interop entirely.
|
||||
/// When set, the service will use this client instead of creating one from IMxProxy.
|
||||
/// </summary>
|
||||
public OpcUaServiceBuilder WithMxAccessClient(IMxAccessClient? client)
|
||||
{
|
||||
_mxAccessClient = client;
|
||||
_mxAccessClientSet = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaServiceBuilder WithHierarchy(List<GalaxyObjectInfo> hierarchy, List<GalaxyAttributeInfo> attributes)
|
||||
{
|
||||
if (!_galaxyRepositorySet)
|
||||
{
|
||||
var fake = new FakeBuilderGalaxyRepository();
|
||||
_galaxyRepository = fake;
|
||||
_galaxyRepositorySet = true;
|
||||
}
|
||||
|
||||
if (_galaxyRepository is FakeBuilderGalaxyRepository fakeRepo)
|
||||
{
|
||||
fakeRepo.Hierarchy = hierarchy;
|
||||
fakeRepo.Attributes = attributes;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaServiceBuilder DisableDashboard()
|
||||
{
|
||||
_config.Dashboard.Enabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaServiceBuilder DisableChangeDetection()
|
||||
{
|
||||
_config.GalaxyRepository.ChangeDetectionIntervalSeconds = int.MaxValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OpcUaService Build()
|
||||
{
|
||||
return new OpcUaService(
|
||||
_config,
|
||||
_mxProxySet ? _mxProxy : null,
|
||||
_galaxyRepositorySet ? _galaxyRepository : null,
|
||||
_mxAccessClientSet ? _mxAccessClient : null,
|
||||
_mxAccessClientSet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal fake repository used by WithHierarchy for convenience.
|
||||
/// </summary>
|
||||
private class FakeBuilderGalaxyRepository : IGalaxyRepository
|
||||
{
|
||||
public event System.Action? OnGalaxyChanged;
|
||||
public List<GalaxyObjectInfo> Hierarchy { get; set; } = new();
|
||||
public List<GalaxyAttributeInfo> Attributes { get; set; } = new();
|
||||
|
||||
public System.Threading.Tasks.Task<List<GalaxyObjectInfo>> GetHierarchyAsync(System.Threading.CancellationToken ct = default)
|
||||
=> System.Threading.Tasks.Task.FromResult(Hierarchy);
|
||||
public System.Threading.Tasks.Task<List<GalaxyAttributeInfo>> GetAttributesAsync(System.Threading.CancellationToken ct = default)
|
||||
=> System.Threading.Tasks.Task.FromResult(Attributes);
|
||||
public System.Threading.Tasks.Task<System.DateTime?> GetLastDeployTimeAsync(System.Threading.CancellationToken ct = default)
|
||||
=> System.Threading.Tasks.Task.FromResult<System.DateTime?>(System.DateTime.UtcNow);
|
||||
public System.Threading.Tasks.Task<bool> TestConnectionAsync(System.Threading.CancellationToken ct = default)
|
||||
=> System.Threading.Tasks.Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user