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:
Joseph Doherty
2026-03-25 06:22:31 -04:00
parent a0edac81fb
commit 44177acf64
5 changed files with 385 additions and 6 deletions

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
{
/// <summary>
/// Reusable test data matching the Galaxy hierarchy from gr/layout.md.
/// </summary>
public static class TestData
{
public static List<GalaxyObjectInfo> CreateStandardHierarchy()
{
return new List<GalaxyObjectInfo>
{
new GalaxyObjectInfo { GobjectId = 1, TagName = "DEV", ContainedName = "DEV", BrowseName = "DEV", ParentGobjectId = 0, IsArea = true },
new GalaxyObjectInfo { GobjectId = 2, TagName = "TestArea", ContainedName = "TestArea", BrowseName = "TestArea", ParentGobjectId = 1, IsArea = true },
new GalaxyObjectInfo { GobjectId = 3, TagName = "TestMachine_001", ContainedName = "TestMachine_001", BrowseName = "TestMachine_001", ParentGobjectId = 2, IsArea = false },
new GalaxyObjectInfo { GobjectId = 4, TagName = "DelmiaReceiver_001", ContainedName = "DelmiaReceiver", BrowseName = "DelmiaReceiver", ParentGobjectId = 3, IsArea = false },
new GalaxyObjectInfo { GobjectId = 5, TagName = "MESReceiver_001", ContainedName = "MESReceiver", BrowseName = "MESReceiver", ParentGobjectId = 3, IsArea = false },
};
}
public static List<GalaxyAttributeInfo> CreateStandardAttributes()
{
return new List<GalaxyAttributeInfo>
{
new GalaxyAttributeInfo { GobjectId = 3, TagName = "TestMachine_001", AttributeName = "MachineID", FullTagReference = "TestMachine_001.MachineID", MxDataType = 5, IsArray = false },
new GalaxyAttributeInfo { GobjectId = 3, TagName = "TestMachine_001", AttributeName = "MachineCode", FullTagReference = "TestMachine_001.MachineCode", MxDataType = 5, IsArray = false },
new GalaxyAttributeInfo { GobjectId = 4, TagName = "DelmiaReceiver_001", AttributeName = "DownloadPath", FullTagReference = "DelmiaReceiver_001.DownloadPath", MxDataType = 5, IsArray = false },
new GalaxyAttributeInfo { GobjectId = 4, TagName = "DelmiaReceiver_001", AttributeName = "JobStepNumber", FullTagReference = "DelmiaReceiver_001.JobStepNumber", MxDataType = 2, IsArray = false },
new GalaxyAttributeInfo { GobjectId = 5, TagName = "MESReceiver_001", AttributeName = "MoveInBatchID", FullTagReference = "MESReceiver_001.MoveInBatchID", MxDataType = 5, IsArray = false },
new GalaxyAttributeInfo { GobjectId = 5, TagName = "MESReceiver_001", AttributeName = "MoveInPartNumbers", FullTagReference = "MESReceiver_001.MoveInPartNumbers[]", MxDataType = 5, IsArray = true, ArrayDimension = 50 },
};
}
public static List<GalaxyObjectInfo> CreateMinimalHierarchy()
{
return new List<GalaxyObjectInfo>
{
new GalaxyObjectInfo { GobjectId = 1, TagName = "TestObj", BrowseName = "TestObj", ParentGobjectId = 0, IsArea = false }
};
}
public static List<GalaxyAttributeInfo> CreateMinimalAttributes()
{
return new List<GalaxyAttributeInfo>
{
new GalaxyAttributeInfo { GobjectId = 1, TagName = "TestObj", AttributeName = "TestAttr", FullTagReference = "TestObj.TestAttr", MxDataType = 5, IsArray = false }
};
}
}
}