test(runtime): cover disabled-array + zero-length in artifact parity round-trip (review)
This commit is contained in:
+51
-2
@@ -102,6 +102,9 @@ public sealed class DeploymentArtifactArrayParityTests
|
|||||||
// round-trip (it can't be unit-tested directly because it's private). A truly malformed TagConfig
|
// round-trip (it can't be unit-tested directly because it's private). A truly malformed TagConfig
|
||||||
// string would cause ExtractTagFullName to return "" and break the SequenceEqual on other fields,
|
// string would cause ExtractTagFullName to return "" and break the SequenceEqual on other fields,
|
||||||
// so the throws/malformed path is covered by the composer unit test, not here.
|
// so the throws/malformed path is covered by the composer unit test, not here.
|
||||||
|
// NOTE (review M-1): only the string-type bad-length case is round-tripped here. The
|
||||||
|
// negative/float/overflow arrayLength reject paths are covered on the composer side
|
||||||
|
// (ExtractTagArrayTests) and do not need duplication in this artifact parity test.
|
||||||
var arrayBadLengthTag = new Tag
|
var arrayBadLengthTag = new Tag
|
||||||
{
|
{
|
||||||
TagId = "tag-array-badlen",
|
TagId = "tag-array-badlen",
|
||||||
@@ -113,12 +116,40 @@ public sealed class DeploymentArtifactArrayParityTests
|
|||||||
AccessLevel = TagAccessLevel.Read,
|
AccessLevel = TagAccessLevel.Read,
|
||||||
TagConfig = "{\"FullName\":\"40004\",\"isArray\":true,\"arrayLength\":\"sixteen\"}",
|
TagConfig = "{\"FullName\":\"40004\",\"isArray\":true,\"arrayLength\":\"sixteen\"}",
|
||||||
};
|
};
|
||||||
|
// I-1: isArray:false with a non-zero arrayLength — the artifact-side guard must gate arrayLength
|
||||||
|
// under isArray, so the length is discarded and IsArray==false, ArrayLength==null on both sides.
|
||||||
|
// Pins that a future removal of the isArray gate would be caught here.
|
||||||
|
var arrayDisabledTag = new Tag
|
||||||
|
{
|
||||||
|
TagId = "tag-array-disabled",
|
||||||
|
DriverInstanceId = "drv-modbus",
|
||||||
|
EquipmentId = "eq-1",
|
||||||
|
FolderPath = null,
|
||||||
|
Name = "Setpoint",
|
||||||
|
DataType = "Float",
|
||||||
|
AccessLevel = TagAccessLevel.ReadWrite,
|
||||||
|
TagConfig = "{\"FullName\":\"40005\",\"isArray\":false,\"arrayLength\":8}",
|
||||||
|
};
|
||||||
|
// I-2: isArray:true with arrayLength:0 — zero is a legal explicit dimension bound (e.g. dynamic
|
||||||
|
// array with declared-but-unset size). Must decode to IsArray==true, ArrayLength==0u on both sides.
|
||||||
|
// Pins that a future change treating 0 as absent would be caught here.
|
||||||
|
var arrayZeroLengthTag = new Tag
|
||||||
|
{
|
||||||
|
TagId = "tag-array-zerolen",
|
||||||
|
DriverInstanceId = "drv-modbus",
|
||||||
|
EquipmentId = "eq-1",
|
||||||
|
FolderPath = null,
|
||||||
|
Name = "Empty",
|
||||||
|
DataType = "Int32",
|
||||||
|
AccessLevel = TagAccessLevel.Read,
|
||||||
|
TagConfig = "{\"FullName\":\"40006\",\"isArray\":true,\"arrayLength\":0}",
|
||||||
|
};
|
||||||
|
|
||||||
var areas = new[] { area };
|
var areas = new[] { area };
|
||||||
var lines = new[] { line };
|
var lines = new[] { line };
|
||||||
var equipment = new[] { equip };
|
var equipment = new[] { equip };
|
||||||
var drivers = new[] { driver };
|
var drivers = new[] { driver };
|
||||||
var tags = new[] { arrayBoundedTag, scalarTag, arrayUnboundedTag, arrayBadLengthTag };
|
var tags = new[] { arrayBoundedTag, scalarTag, arrayUnboundedTag, arrayBadLengthTag, arrayDisabledTag, arrayZeroLengthTag };
|
||||||
var namespaces = new[] { ns };
|
var namespaces = new[] { ns };
|
||||||
|
|
||||||
// ---- Side 1: the live-edit composer ----
|
// ---- Side 1: the live-edit composer ----
|
||||||
@@ -142,13 +173,15 @@ public sealed class DeploymentArtifactArrayParityTests
|
|||||||
ToSnapshot(scalarTag),
|
ToSnapshot(scalarTag),
|
||||||
ToSnapshot(arrayUnboundedTag),
|
ToSnapshot(arrayUnboundedTag),
|
||||||
ToSnapshot(arrayBadLengthTag),
|
ToSnapshot(arrayBadLengthTag),
|
||||||
|
ToSnapshot(arrayDisabledTag),
|
||||||
|
ToSnapshot(arrayZeroLengthTag),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var decoded = DeploymentArtifact.ParseComposition(blob);
|
var decoded = DeploymentArtifact.ParseComposition(blob);
|
||||||
|
|
||||||
// ---- Full byte-parity: every field, same order (positional-record value equality) ----
|
// ---- Full byte-parity: every field, same order (positional-record value equality) ----
|
||||||
decoded.EquipmentTags.Count.ShouldBe(4);
|
decoded.EquipmentTags.Count.ShouldBe(6);
|
||||||
decoded.EquipmentTags.SequenceEqual(composed.EquipmentTags).ShouldBeTrue();
|
decoded.EquipmentTags.SequenceEqual(composed.EquipmentTags).ShouldBeTrue();
|
||||||
|
|
||||||
// Spell out the array fields per-tag so a divergence names the offending tag.
|
// Spell out the array fields per-tag so a divergence names the offending tag.
|
||||||
@@ -177,6 +210,22 @@ public sealed class DeploymentArtifactArrayParityTests
|
|||||||
arrayBadLength.ArrayLength.ShouldBeNull();
|
arrayBadLength.ArrayLength.ShouldBeNull();
|
||||||
composed.EquipmentTags.Single(t => t.TagId == "tag-array-badlen").IsArray.ShouldBeTrue();
|
composed.EquipmentTags.Single(t => t.TagId == "tag-array-badlen").IsArray.ShouldBeTrue();
|
||||||
composed.EquipmentTags.Single(t => t.TagId == "tag-array-badlen").ArrayLength.ShouldBeNull();
|
composed.EquipmentTags.Single(t => t.TagId == "tag-array-badlen").ArrayLength.ShouldBeNull();
|
||||||
|
|
||||||
|
// I-1: isArray:false with a non-zero arrayLength ⇒ (false, null) on both sides.
|
||||||
|
// The artifact-side isArray gate must suppress arrayLength even when it is present in the blob.
|
||||||
|
var arrayDisabled = decoded.EquipmentTags.Single(t => t.TagId == "tag-array-disabled");
|
||||||
|
arrayDisabled.IsArray.ShouldBeFalse();
|
||||||
|
arrayDisabled.ArrayLength.ShouldBeNull();
|
||||||
|
composed.EquipmentTags.Single(t => t.TagId == "tag-array-disabled").IsArray.ShouldBeFalse();
|
||||||
|
composed.EquipmentTags.Single(t => t.TagId == "tag-array-disabled").ArrayLength.ShouldBeNull();
|
||||||
|
|
||||||
|
// I-2: isArray:true with arrayLength:0 ⇒ (true, 0u) on both sides.
|
||||||
|
// Zero is a legal explicit dimension bound; it must not be treated as absent.
|
||||||
|
var arrayZeroLength = decoded.EquipmentTags.Single(t => t.TagId == "tag-array-zerolen");
|
||||||
|
arrayZeroLength.IsArray.ShouldBeTrue();
|
||||||
|
arrayZeroLength.ArrayLength.ShouldBe(0u);
|
||||||
|
composed.EquipmentTags.Single(t => t.TagId == "tag-array-zerolen").IsArray.ShouldBeTrue();
|
||||||
|
composed.EquipmentTags.Single(t => t.TagId == "tag-array-zerolen").ArrayLength.ShouldBe(0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>The Pascal-case snapshot a <see cref="Tag"/> EF entity serialises to in the artifact
|
/// <summary>The Pascal-case snapshot a <see cref="Tag"/> EF entity serialises to in the artifact
|
||||||
|
|||||||
Reference in New Issue
Block a user