feat(deploy): activate full DraftValidator gate (reject on any validation error)
This commit is contained in:
@@ -186,6 +186,117 @@ public sealed class DraftValidatorTests
|
||||
errors.ShouldContain(e => e.Code == "UnsSegmentInvalid");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Full-config probe — proves a realistic canonical deployed config passes ALL rules.
|
||||
// This guards the deploy-path gate flip (reject-on-ANY-error): if a clean canonical
|
||||
// config fired any rule, flipping the gate would block every deploy.
|
||||
// ------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>Probe for the deploy-path gate activation: a full, realistic config modelling
|
||||
/// the REAL deployed shape (a SystemPlatform namespace + GalaxyMxGateway driver + SystemPlatform
|
||||
/// Tags with EquipmentId=null from the OtOpcUa seed, PLUS an Equipment namespace + Modbus driver
|
||||
/// + UNS area/line + canonical Equipment rows + VirtualTags from the company overlay) must
|
||||
/// produce ZERO validation errors so the reject-on-any-error gate is safe to activate.</summary>
|
||||
[Fact]
|
||||
public void Full_realistic_config_passes_all_rules()
|
||||
{
|
||||
// SystemPlatform side (OtOpcUa seed shape): Galaxy hierarchy, no Equipment, Tags carry EquipmentId=null.
|
||||
var spNamespace = new Namespace
|
||||
{
|
||||
NamespaceId = "MAIN-OPCUA-systemplatform",
|
||||
ClusterId = "MAIN",
|
||||
Kind = NamespaceKind.SystemPlatform,
|
||||
NamespaceUri = "urn:zb:main:systemplatform",
|
||||
};
|
||||
var spDriver = new DriverInstance
|
||||
{
|
||||
DriverInstanceId = "main-galaxy",
|
||||
ClusterId = "MAIN",
|
||||
NamespaceId = spNamespace.NamespaceId,
|
||||
Name = "Galaxy",
|
||||
DriverType = "GalaxyMxGateway",
|
||||
DriverConfig = "{}",
|
||||
};
|
||||
|
||||
// Equipment side (company overlay): Modbus driver in an Equipment namespace + UNS + canonical equipment.
|
||||
var eqNamespace = new Namespace
|
||||
{
|
||||
NamespaceId = "MAIN-OPCUA-equipment",
|
||||
ClusterId = "MAIN",
|
||||
Kind = NamespaceKind.Equipment,
|
||||
NamespaceUri = "urn:zb:main:equipment",
|
||||
};
|
||||
var eqDriver = new DriverInstance
|
||||
{
|
||||
DriverInstanceId = "main-modbus",
|
||||
ClusterId = "MAIN",
|
||||
NamespaceId = eqNamespace.NamespaceId,
|
||||
Name = "Modbus",
|
||||
DriverType = "Modbus",
|
||||
DriverConfig = "{}",
|
||||
};
|
||||
|
||||
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 EquipmentIds — derived from the EquipmentUuid via the same rule the overlay loader uses.
|
||||
var rinserUuid = Guid.NewGuid();
|
||||
var fillerUuid = Guid.NewGuid();
|
||||
var rinser = new Equipment
|
||||
{
|
||||
EquipmentUuid = rinserUuid,
|
||||
EquipmentId = DraftValidator.DeriveEquipmentId(rinserUuid),
|
||||
Name = "rinser-01",
|
||||
DriverInstanceId = eqDriver.DriverInstanceId,
|
||||
UnsLineId = line.UnsLineId,
|
||||
MachineCode = "machine_001",
|
||||
ZTag = null,
|
||||
SAPID = null,
|
||||
};
|
||||
var filler = new Equipment
|
||||
{
|
||||
EquipmentUuid = fillerUuid,
|
||||
EquipmentId = DraftValidator.DeriveEquipmentId(fillerUuid),
|
||||
Name = "filler-02",
|
||||
DriverInstanceId = eqDriver.DriverInstanceId,
|
||||
UnsLineId = line.UnsLineId,
|
||||
MachineCode = "machine_002",
|
||||
ZTag = null,
|
||||
SAPID = null,
|
||||
};
|
||||
|
||||
var draft = new DraftSnapshot
|
||||
{
|
||||
GenerationId = 0,
|
||||
ClusterId = string.Empty, // global snapshot — matches DraftSnapshotFactory.FromConfigDbAsync
|
||||
// Enterprise/Site left null — matches the deploy path's conservative fallback
|
||||
Namespaces = [spNamespace, eqNamespace],
|
||||
DriverInstances = [spDriver, eqDriver],
|
||||
UnsAreas = [area],
|
||||
UnsLines = [line],
|
||||
Equipment = [rinser, filler],
|
||||
Tags =
|
||||
[
|
||||
// SystemPlatform tags from the seed: EquipmentId null, Galaxy folder hierarchy.
|
||||
BuildTag(equipmentId: null, name: "PV", folderPath: "Area.Tank01"),
|
||||
BuildTag(equipmentId: null, name: "SP", folderPath: "Area.Tank01"),
|
||||
],
|
||||
VirtualTags =
|
||||
[
|
||||
// One per equipment, names distinct within each owning equipment.
|
||||
BuildVirtualTag(equipmentId: rinser.EquipmentId, name: "oee"),
|
||||
BuildVirtualTag(equipmentId: filler.EquipmentId, name: "throughput"),
|
||||
],
|
||||
};
|
||||
|
||||
var errors = DraftValidator.Validate(draft);
|
||||
|
||||
errors.ShouldBeEmpty(
|
||||
"a realistic canonical deployed config must pass every DraftValidator rule so the " +
|
||||
"reject-on-any-error deploy gate is safe; firing rules: " +
|
||||
string.Join("; ", errors.Select(e => $"[{e.Code}] {e.Message}")));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// ValidateNoEquipmentSignalNameCollision — Tag/VirtualTag NodeId collision
|
||||
// ------------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user