test(config): DraftValidator accepts driver-less equipment + driverless equipment namespace

This commit is contained in:
Joseph Doherty
2026-06-08 06:56:22 -04:00
parent c688899134
commit 53eb3fcda5
@@ -297,6 +297,65 @@ public sealed class DraftValidatorTests
string.Join("; ", errors.Select(e => $"[{e.Code}] {e.Message}")));
}
// ------------------------------------------------------------------------------------
// Driver-less Equipment namespace — core safety claim (feat/driverless-equipment-namespace)
// ------------------------------------------------------------------------------------
/// <summary>Characterisation test: a draft that contains an Equipment-kind namespace with
/// ZERO <see cref="DriverInstance"/> rows, and a single <see cref="Equipment"/> whose
/// <see cref="Equipment.DriverInstanceId"/> is <see langword="null"/>, must pass
/// <see cref="DraftValidator.Validate"/> with no errors. This locks in the design's core
/// safety claim — the validator must never implicitly require a driver for Equipment-namespace
/// equipment.</summary>
[Fact]
public void Validate_accepts_driverless_equipment_in_driverless_equipment_namespace()
{
// An Equipment-kind namespace — no DriverInstance rows at all for this namespace.
var eqNamespace = new Namespace
{
NamespaceId = "MAIN-OPCUA-equipment",
ClusterId = "MAIN",
Kind = NamespaceKind.Equipment,
NamespaceUri = "urn:zb:main:equipment",
};
// UNS topology required by ValidateUnsSegments / ValidatePathLength.
var area = new UnsArea { UnsAreaId = "area-filling", ClusterId = "MAIN", Name = "filling" };
var line = new UnsLine { UnsLineId = "line-1", UnsAreaId = area.UnsAreaId, Name = "line-1" };
// Canonical EquipmentId derived from UUID — satisfies ValidateEquipmentIdDerivation.
var uuid = Guid.NewGuid();
var equipment = new Equipment
{
EquipmentUuid = uuid,
EquipmentId = DraftValidator.DeriveEquipmentId(uuid),
Name = "rinser-01",
DriverInstanceId = null, // ← driver-less: the property under test
UnsLineId = line.UnsLineId,
MachineCode = "machine_001",
ZTag = null,
SAPID = null,
};
var draft = new DraftSnapshot
{
GenerationId = 0,
ClusterId = string.Empty, // global snapshot — matches DraftSnapshotFactory.FromConfigDbAsync
Namespaces = [eqNamespace], // Equipment namespace present
DriverInstances = [], // ← zero drivers: the other half of the safety claim
UnsAreas = [area],
UnsLines = [line],
Equipment = [equipment],
};
var errors = DraftValidator.Validate(draft);
errors.ShouldBeEmpty(
"a driver-less Equipment in an Equipment-kind namespace with no DriverInstances must pass " +
"all validator rules; firing rules: " +
string.Join("; ", errors.Select(e => $"[{e.Code}] {e.Message}")));
}
// ------------------------------------------------------------------------------------
// ValidateNoEquipmentSignalNameCollision — Tag/VirtualTag NodeId collision
// ------------------------------------------------------------------------------------