feat(adminui): alias DTO + Galaxy gateway lookup + Source/IsAlias on tag rows

This commit is contained in:
Joseph Doherty
2026-06-11 21:05:02 -04:00
parent 2ba2f8a899
commit 4b4738a891
5 changed files with 291 additions and 4 deletions
@@ -0,0 +1,187 @@
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
/// <summary>
/// Verifies the read-side surface for Galaxy alias tags: that
/// <see cref="UnsTreeService.LoadGalaxyGatewaysForEquipmentAsync"/> finds the
/// <c>GalaxyMxGateway</c> drivers in the equipment's cluster (and only those), and that
/// <see cref="UnsTreeService.LoadTagsForEquipmentAsync"/> flags alias tags (those bound to a Galaxy
/// gateway) with <c>IsAlias = true</c> and a <c>Source</c> derived from the tag's <c>TagConfig</c>.
/// </summary>
[Trait("Category", "Unit")]
public sealed class UnsTreeServiceAliasTagTests
{
private const string ClusterId = "MAIN";
private const string EquipmentId = "EQ-ALIAS-1";
private const string GatewayDriverId = "DRV-GALAXY";
private const string ModbusDriverId = "DRV-MODBUS";
/// <summary>
/// Seeds a cluster with: a SystemPlatform namespace + GalaxyMxGateway driver, an Equipment-kind
/// namespace + Modbus driver, and an area→line→equipment path. Returns the InMemory db name.
/// </summary>
private static string SeedCluster(bool withGalaxyGateway = true)
{
var dbName = $"uns-alias-{Guid.NewGuid():N}";
using var db = UnsTreeTestDb.CreateNamed(dbName);
db.ServerClusters.Add(new ServerCluster
{
ClusterId = ClusterId,
Name = "Main",
Enterprise = "zb",
Site = "warsaw-west",
RedundancyMode = RedundancyMode.None,
CreatedBy = "test",
});
db.UnsAreas.Add(new UnsArea { UnsAreaId = "AREA-1", ClusterId = ClusterId, Name = "a" });
db.UnsLines.Add(new UnsLine { UnsLineId = "LINE-1", UnsAreaId = "AREA-1", Name = "l" });
db.Equipment.Add(new Equipment
{
EquipmentId = EquipmentId,
EquipmentUuid = Guid.NewGuid(),
UnsLineId = "LINE-1",
Name = "machine-1",
MachineCode = "machine_001",
});
// SystemPlatform namespace hosting the Galaxy gateway.
db.Namespaces.Add(new Namespace
{
NamespaceId = "NS-SP",
ClusterId = ClusterId,
Kind = NamespaceKind.SystemPlatform,
NamespaceUri = "urn:zb:sp",
});
if (withGalaxyGateway)
{
db.DriverInstances.Add(new DriverInstance
{
DriverInstanceId = GatewayDriverId,
ClusterId = ClusterId,
NamespaceId = "NS-SP",
Name = "galaxy gateway",
DriverType = "GalaxyMxGateway",
DriverConfig = "{\"Galaxy\":{}}",
});
}
// Equipment-kind namespace hosting an ordinary (non-Galaxy) driver.
db.Namespaces.Add(new Namespace
{
NamespaceId = "NS-EQ",
ClusterId = ClusterId,
Kind = NamespaceKind.Equipment,
NamespaceUri = "urn:zb:eq",
});
db.DriverInstances.Add(new DriverInstance
{
DriverInstanceId = ModbusDriverId,
ClusterId = ClusterId,
NamespaceId = "NS-EQ",
Name = "modbus driver",
DriverType = "Modbus",
DriverConfig = "{}",
});
db.SaveChanges();
return dbName;
}
/// <summary>
/// The gateway lookup returns exactly the GalaxyMxGateway driver (with its id, a display string,
/// and its DriverConfig) and never the cluster's Modbus driver.
/// </summary>
[Fact]
public async Task LoadGalaxyGatewaysForEquipment_returns_only_galaxy_gateway()
{
var dbName = SeedCluster();
var service = new UnsTreeService(UnsTreeTestDb.Factory(dbName));
var gateways = await service.LoadGalaxyGatewaysForEquipmentAsync(EquipmentId);
gateways.Count.ShouldBe(1);
gateways[0].DriverInstanceId.ShouldBe(GatewayDriverId);
gateways[0].Display.ShouldContain(GatewayDriverId);
gateways[0].Display.ShouldContain("galaxy gateway");
gateways[0].DriverConfig.ShouldBe("{\"Galaxy\":{}}");
gateways.ShouldNotContain(g => g.DriverInstanceId == ModbusDriverId);
}
/// <summary>An equipment whose cluster has no GalaxyMxGateway driver yields an empty list.</summary>
[Fact]
public async Task LoadGalaxyGatewaysForEquipment_returns_empty_when_no_gateway()
{
var dbName = SeedCluster(withGalaxyGateway: false);
var service = new UnsTreeService(UnsTreeTestDb.Factory(dbName));
var gateways = await service.LoadGalaxyGatewaysForEquipmentAsync(EquipmentId);
gateways.ShouldBeEmpty();
}
/// <summary>
/// A tag bound to the Galaxy gateway is an alias (<c>IsAlias = true</c>, <c>Source</c> derived from
/// its TagConfig FullName); a tag bound to the Modbus driver is not (<c>IsAlias = false</c>,
/// <c>Source = null</c>).
/// </summary>
[Fact]
public async Task LoadTagsForEquipment_flags_galaxy_alias_rows()
{
var dbName = SeedCluster();
using (var db = UnsTreeTestDb.CreateNamed(dbName))
{
db.Tags.Add(new Tag
{
TagId = "TAG-ALIAS",
DriverInstanceId = GatewayDriverId,
EquipmentId = EquipmentId,
Name = "aliased-speed",
DataType = "Float",
AccessLevel = TagAccessLevel.Read,
TagConfig = "{\"FullName\":\"TestMachine_020.Speed\"}",
});
db.Tags.Add(new Tag
{
TagId = "TAG-NORMAL",
DriverInstanceId = ModbusDriverId,
EquipmentId = EquipmentId,
Name = "raw-speed",
DataType = "Int32",
AccessLevel = TagAccessLevel.Read,
TagConfig = "{}",
});
db.SaveChanges();
}
var service = new UnsTreeService(UnsTreeTestDb.Factory(dbName));
var rows = await service.LoadTagsForEquipmentAsync(EquipmentId);
var alias = rows.ShouldHaveSingleItem(r => r.TagId == "TAG-ALIAS");
alias.IsAlias.ShouldBeTrue();
alias.Source.ShouldBe("galaxy:TestMachine_020.Speed");
var normal = rows.ShouldHaveSingleItem(r => r.TagId == "TAG-NORMAL");
normal.IsAlias.ShouldBeFalse();
normal.Source.ShouldBeNull();
}
}
/// <summary>Small Shouldly-style helper for "exactly one match" assertions used by these tests.</summary>
internal static class SingleItemAssertions
{
public static T ShouldHaveSingleItem<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
var matches = source.Where(predicate).ToList();
matches.Count.ShouldBe(1);
return matches[0];
}
}