feat(composer): admit GalaxyMxGateway-backed equipment alias tags (+byte-parity)
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the live-edit compose seam (<see cref="Phase7Composer.Compose"/>) admits a Galaxy
|
||||
/// alias tag — an equipment-scoped <see cref="Tag"/> (non-null <see cref="Tag.EquipmentId"/>)
|
||||
/// bound to a <c>GalaxyMxGateway</c> driver that lives in a <c>SystemPlatform</c>-kind namespace.
|
||||
/// The broadened equipment-tag filter must surface the alias under
|
||||
/// <see cref="Phase7CompositionResult.EquipmentTags"/> (carrying its driver-side FullName) while a
|
||||
/// sibling SystemPlatform mirror tag (null EquipmentId) on the same driver stays in
|
||||
/// <see cref="Phase7CompositionResult.GalaxyTags"/> and never double-counts.
|
||||
/// </summary>
|
||||
public sealed class Phase7ComposerAliasTagTests
|
||||
{
|
||||
/// <summary>A <c>GalaxyMxGateway</c> driver in a SystemPlatform namespace carries two tags: an
|
||||
/// equipment-scoped alias (EquipmentId set, FolderPath null, TagConfig FullName = the Galaxy ref)
|
||||
/// and a SystemPlatform mirror (EquipmentId null, FolderPath set). Compose must put the alias in
|
||||
/// EquipmentTags with its FullName and keep the mirror in GalaxyTags only.</summary>
|
||||
[Fact]
|
||||
public void Compose_admits_galaxy_alias_tag_in_equipment_tags()
|
||||
{
|
||||
var ns = new Namespace
|
||||
{
|
||||
NamespaceId = "ns-sp",
|
||||
ClusterId = "c1",
|
||||
Kind = NamespaceKind.SystemPlatform,
|
||||
NamespaceUri = "urn:sp",
|
||||
};
|
||||
var driver = new DriverInstance
|
||||
{
|
||||
DriverInstanceId = "drv-galaxy",
|
||||
ClusterId = "c1",
|
||||
NamespaceId = "ns-sp",
|
||||
Name = "Galaxy1",
|
||||
DriverType = "GalaxyMxGateway",
|
||||
DriverConfig = "{}",
|
||||
};
|
||||
var area = new UnsArea { UnsAreaId = "area-1", ClusterId = "c1", Name = "filling" };
|
||||
var line = new UnsLine { UnsLineId = "line-1", UnsAreaId = "area-1", Name = "line-1" };
|
||||
var equip = new Equipment
|
||||
{
|
||||
EquipmentId = "eq-1",
|
||||
DriverInstanceId = "drv-galaxy",
|
||||
UnsLineId = "line-1",
|
||||
Name = "TestMachine_020",
|
||||
MachineCode = "TESTMACHINE_020",
|
||||
};
|
||||
|
||||
// (a) Equipment-scoped alias: EquipmentId set, FolderPath null, FullName = Galaxy ref.
|
||||
var aliasTag = new Tag
|
||||
{
|
||||
TagId = "tag-alias",
|
||||
DriverInstanceId = "drv-galaxy",
|
||||
EquipmentId = "eq-1",
|
||||
FolderPath = null,
|
||||
Name = "TestChangingInt",
|
||||
DataType = "Int32",
|
||||
AccessLevel = TagAccessLevel.Read,
|
||||
TagConfig = "{\"FullName\":\"TestMachine_020.TestChangingInt\"}",
|
||||
};
|
||||
|
||||
// (b) SystemPlatform mirror: EquipmentId null, FolderPath set → stays a Galaxy tag.
|
||||
var mirrorTag = new Tag
|
||||
{
|
||||
TagId = "tag-mirror",
|
||||
DriverInstanceId = "drv-galaxy",
|
||||
EquipmentId = null,
|
||||
FolderPath = "TestMachine_020",
|
||||
Name = "Speed",
|
||||
DataType = "Float",
|
||||
AccessLevel = TagAccessLevel.Read,
|
||||
TagConfig = "{\"FullName\":\"TestMachine_020.Speed\"}",
|
||||
};
|
||||
|
||||
var result = Phase7Composer.Compose(
|
||||
new[] { area }, new[] { line }, new[] { equip },
|
||||
new[] { driver }, Array.Empty<ScriptedAlarm>(),
|
||||
new[] { aliasTag, mirrorTag }, new[] { ns });
|
||||
|
||||
// (a) the alias survives composition as an equipment tag, carrying its Galaxy FullName.
|
||||
var alias = result.EquipmentTags.ShouldHaveSingleItem();
|
||||
alias.TagId.ShouldBe("tag-alias");
|
||||
alias.EquipmentId.ShouldBe("eq-1");
|
||||
alias.DriverInstanceId.ShouldBe("drv-galaxy");
|
||||
alias.Name.ShouldBe("TestChangingInt");
|
||||
alias.DataType.ShouldBe("Int32");
|
||||
alias.FullName.ShouldBe("TestMachine_020.TestChangingInt");
|
||||
|
||||
// (b) the SystemPlatform mirror stays in GalaxyTags and is NOT double-counted as equipment.
|
||||
result.GalaxyTags.ShouldContain(g => g.TagId == "tag-mirror");
|
||||
result.GalaxyTags.ShouldNotContain(g => g.TagId == "tag-alias");
|
||||
result.EquipmentTags.ShouldNotContain(t => t.TagId == "tag-mirror");
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
using System.Text.Json;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the artifact-decode mirror (<see cref="DeploymentArtifact.ParseComposition(System.ReadOnlySpan{byte})"/>)
|
||||
/// admits a Galaxy alias tag — an equipment-scoped tag (non-null <c>EquipmentId</c>) bound to a
|
||||
/// <c>GalaxyMxGateway</c> driver in a <c>SystemPlatform</c>-kind namespace — into the decoded
|
||||
/// <c>EquipmentTags</c> with byte-parity to the live-edit composer path: same FullName, EquipmentId,
|
||||
/// DriverInstanceId, Name, DataType. The composer broadens the same filter by DriverType, so both
|
||||
/// data-contract sites must agree on which tags qualify.
|
||||
/// </summary>
|
||||
public sealed class DeploymentArtifactAliasParityTests
|
||||
{
|
||||
/// <summary>An artifact JSON blob with a GalaxyMxGateway driver in a SystemPlatform (Kind=1)
|
||||
/// namespace and one equipment-scoped alias tag (EquipmentId set, FolderPath null, FullName = the
|
||||
/// Galaxy ref). Decode must surface the alias in EquipmentTags carrying its driver-side FullName.</summary>
|
||||
[Fact]
|
||||
public void ParseComposition_admits_galaxy_alias_tag_in_equipment_tags()
|
||||
{
|
||||
var blob = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
Namespaces = new[]
|
||||
{
|
||||
new { NamespaceId = "ns-sp", Kind = 1 }, // NamespaceKind.SystemPlatform
|
||||
},
|
||||
DriverInstances = new[]
|
||||
{
|
||||
new { DriverInstanceId = "drv-galaxy", DriverType = "GalaxyMxGateway", DriverConfig = "{}", NamespaceId = "ns-sp" },
|
||||
},
|
||||
Tags = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
TagId = "tag-alias",
|
||||
DriverInstanceId = "drv-galaxy",
|
||||
EquipmentId = "eq-1",
|
||||
Name = "TestChangingInt",
|
||||
FolderPath = (string?)null,
|
||||
DataType = "Int32",
|
||||
TagConfig = "{\"FullName\":\"TestMachine_020.TestChangingInt\"}",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var c = DeploymentArtifact.ParseComposition(blob);
|
||||
|
||||
var alias = c.EquipmentTags.ShouldHaveSingleItem();
|
||||
alias.TagId.ShouldBe("tag-alias");
|
||||
alias.EquipmentId.ShouldBe("eq-1");
|
||||
alias.DriverInstanceId.ShouldBe("drv-galaxy");
|
||||
alias.Name.ShouldBe("TestChangingInt");
|
||||
alias.DataType.ShouldBe("Int32");
|
||||
alias.FolderPath.ShouldBe(string.Empty);
|
||||
alias.FullName.ShouldBe("TestMachine_020.TestChangingInt");
|
||||
}
|
||||
|
||||
/// <summary>An equipment-scoped tag bound to a non-Galaxy driver in a SystemPlatform namespace is
|
||||
/// NOT a Galaxy alias and must stay excluded from EquipmentTags — the broadened clause keys on the
|
||||
/// GalaxyMxGateway DriverType, not on the namespace kind, so the contract narrows correctly.</summary>
|
||||
[Fact]
|
||||
public void ParseComposition_excludes_non_galaxy_systemplatform_equipment_tag()
|
||||
{
|
||||
var blob = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
Namespaces = new[]
|
||||
{
|
||||
new { NamespaceId = "ns-sp", Kind = 1 }, // NamespaceKind.SystemPlatform
|
||||
},
|
||||
DriverInstances = new[]
|
||||
{
|
||||
new { DriverInstanceId = "drv-modbus", DriverType = "Modbus", DriverConfig = "{}", NamespaceId = "ns-sp" },
|
||||
},
|
||||
Tags = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
TagId = "tag-x",
|
||||
DriverInstanceId = "drv-modbus",
|
||||
EquipmentId = "eq-1",
|
||||
Name = "Source",
|
||||
FolderPath = (string?)null,
|
||||
DataType = "Int32",
|
||||
TagConfig = "{\"FullName\":\"TestMachine_020.Source\"}",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var c = DeploymentArtifact.ParseComposition(blob);
|
||||
|
||||
c.EquipmentTags.ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user