feat(validation): allow GalaxyMxGateway under Equipment; rename Galaxy-tag FullName check
This commit is contained in:
@@ -31,14 +31,13 @@ public static class DraftValidator
|
|||||||
ValidateSameClusterNamespaceBinding(draft, errors);
|
ValidateSameClusterNamespaceBinding(draft, errors);
|
||||||
ValidateReservationPreflight(draft, errors);
|
ValidateReservationPreflight(draft, errors);
|
||||||
ValidateEquipmentIdDerivation(draft, errors);
|
ValidateEquipmentIdDerivation(draft, errors);
|
||||||
ValidateDriverNamespaceCompatibility(draft, errors);
|
|
||||||
ValidateNoEquipmentSignalNameCollision(draft, errors);
|
ValidateNoEquipmentSignalNameCollision(draft, errors);
|
||||||
ValidateAliasTagFullName(draft, errors);
|
ValidateGalaxyTagFullName(draft, errors);
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateAliasTagFullName(DraftSnapshot draft, List<ValidationError> errors)
|
private static void ValidateGalaxyTagFullName(DraftSnapshot draft, List<ValidationError> errors)
|
||||||
{
|
{
|
||||||
var typeByDriver = draft.DriverInstances
|
var typeByDriver = draft.DriverInstances
|
||||||
.ToDictionary(d => d.DriverInstanceId, d => d.DriverType, StringComparer.Ordinal);
|
.ToDictionary(d => d.DriverInstanceId, d => d.DriverType, StringComparer.Ordinal);
|
||||||
@@ -48,8 +47,8 @@ public static class DraftValidator
|
|||||||
if (!typeByDriver.TryGetValue(t.DriverInstanceId, out var dtype) || dtype != "GalaxyMxGateway")
|
if (!typeByDriver.TryGetValue(t.DriverInstanceId, out var dtype) || dtype != "GalaxyMxGateway")
|
||||||
continue;
|
continue;
|
||||||
if (string.IsNullOrWhiteSpace(ExtractTagConfigFullName(t.TagConfig)))
|
if (string.IsNullOrWhiteSpace(ExtractTagConfigFullName(t.TagConfig)))
|
||||||
errors.Add(new("AliasTagMissingReference",
|
errors.Add(new("GalaxyTagMissingReference",
|
||||||
$"Alias tag '{t.TagId}' on equipment '{t.EquipmentId}' is missing a Galaxy reference (TagConfig.FullName)",
|
$"Galaxy tag '{t.TagId}' on equipment '{t.EquipmentId}' is missing a Galaxy reference (TagConfig.FullName)",
|
||||||
t.TagId));
|
t.TagId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,28 +221,6 @@ public static class DraftValidator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateDriverNamespaceCompatibility(DraftSnapshot draft, List<ValidationError> errors)
|
|
||||||
{
|
|
||||||
var nsById = draft.Namespaces.ToDictionary(n => n.NamespaceId);
|
|
||||||
|
|
||||||
foreach (var di in draft.DriverInstances)
|
|
||||||
{
|
|
||||||
if (!nsById.TryGetValue(di.NamespaceId, out var ns)) continue;
|
|
||||||
|
|
||||||
var compat = ns.Kind switch
|
|
||||||
{
|
|
||||||
NamespaceKind.SystemPlatform => di.DriverType == "GalaxyMxGateway",
|
|
||||||
NamespaceKind.Equipment => di.DriverType != "GalaxyMxGateway",
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!compat)
|
|
||||||
errors.Add(new("DriverNamespaceKindMismatch",
|
|
||||||
$"DriverInstance '{di.DriverInstanceId}' ({di.DriverType}) is not allowed in {ns.Kind} namespace",
|
|
||||||
di.DriverInstanceId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase 6.3 Stream A.2 + task #148 part 2 — managed pre-publish guard for cluster
|
/// Phase 6.3 Stream A.2 + task #148 part 2 — managed pre-publish guard for cluster
|
||||||
/// topology vs. <see cref="ServerCluster.RedundancyMode"/>. The SQL
|
/// topology vs. <see cref="ServerCluster.RedundancyMode"/>. The SQL
|
||||||
|
|||||||
@@ -123,24 +123,11 @@ public sealed class DraftValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that the canonical Galaxy driver type (GalaxyMxGateway, per PR 7.2 —
|
/// <summary>Verifies that the canonical Galaxy driver type (GalaxyMxGateway, per PR 7.2 —
|
||||||
/// it was "Galaxy" pre-PR-7.2) is allowed in a SystemPlatform namespace, i.e. produces no
|
/// it was "Galaxy" pre-PR-7.2) is now a standard Equipment-kind driver: binding it to an
|
||||||
/// kind-mismatch error.</summary>
|
/// Equipment namespace produces no kind-mismatch error (the SystemPlatform namespace split
|
||||||
|
/// is being retired — Galaxy under Equipment is valid).</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GalaxyMxGateway_driver_in_SystemPlatform_namespace_is_allowed()
|
public void GalaxyMxGateway_driver_in_Equipment_namespace_is_allowed()
|
||||||
{
|
|
||||||
var draft = new DraftSnapshot
|
|
||||||
{
|
|
||||||
GenerationId = 1, ClusterId = "c",
|
|
||||||
Namespaces = [new Namespace { NamespaceId = "ns-1", ClusterId = "c", NamespaceUri = "urn:x", Kind = NamespaceKind.SystemPlatform }],
|
|
||||||
DriverInstances = [new DriverInstance { DriverInstanceId = "d-1", ClusterId = "c", NamespaceId = "ns-1", Name = "drv", DriverType = "GalaxyMxGateway", DriverConfig = "{}" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
DraftValidator.Validate(draft).ShouldNotContain(e => e.Code == "DriverNamespaceKindMismatch");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Verifies that the canonical Galaxy driver type cannot be placed in an Equipment namespace.</summary>
|
|
||||||
[Fact]
|
|
||||||
public void GalaxyMxGateway_driver_in_Equipment_namespace_is_rejected()
|
|
||||||
{
|
{
|
||||||
var draft = new DraftSnapshot
|
var draft = new DraftSnapshot
|
||||||
{
|
{
|
||||||
@@ -149,21 +136,7 @@ public sealed class DraftValidatorTests
|
|||||||
DriverInstances = [new DriverInstance { DriverInstanceId = "d-1", ClusterId = "c", NamespaceId = "ns-1", Name = "drv", DriverType = "GalaxyMxGateway", DriverConfig = "{}" }],
|
DriverInstances = [new DriverInstance { DriverInstanceId = "d-1", ClusterId = "c", NamespaceId = "ns-1", Name = "drv", DriverType = "GalaxyMxGateway", DriverConfig = "{}" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
DraftValidator.Validate(draft).ShouldContain(e => e.Code == "DriverNamespaceKindMismatch");
|
DraftValidator.Validate(draft).ShouldNotContain(e => e.Code == "DriverNamespaceKindMismatch");
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Verifies that a non-Galaxy driver cannot be placed in a SystemPlatform namespace.</summary>
|
|
||||||
[Fact]
|
|
||||||
public void NonGalaxy_driver_in_SystemPlatform_namespace_is_rejected()
|
|
||||||
{
|
|
||||||
var draft = new DraftSnapshot
|
|
||||||
{
|
|
||||||
GenerationId = 1, ClusterId = "c",
|
|
||||||
Namespaces = [new Namespace { NamespaceId = "ns-1", ClusterId = "c", NamespaceUri = "urn:x", Kind = NamespaceKind.SystemPlatform }],
|
|
||||||
DriverInstances = [new DriverInstance { DriverInstanceId = "d-1", ClusterId = "c", NamespaceId = "ns-1", Name = "drv", DriverType = "ModbusTcp", DriverConfig = "{}" }],
|
|
||||||
};
|
|
||||||
|
|
||||||
DraftValidator.Validate(draft).ShouldContain(e => e.Code == "DriverNamespaceKindMismatch");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that all validation errors are reported simultaneously.</summary>
|
/// <summary>Verifies that all validation errors are reported simultaneously.</summary>
|
||||||
@@ -181,7 +154,6 @@ public sealed class DraftValidatorTests
|
|||||||
|
|
||||||
var errors = DraftValidator.Validate(draft);
|
var errors = DraftValidator.Validate(draft);
|
||||||
errors.ShouldContain(e => e.Code == "BadCrossClusterNamespaceBinding");
|
errors.ShouldContain(e => e.Code == "BadCrossClusterNamespaceBinding");
|
||||||
errors.ShouldContain(e => e.Code == "DriverNamespaceKindMismatch");
|
|
||||||
errors.ShouldContain(e => e.Code == "EquipmentIdNotDerived");
|
errors.ShouldContain(e => e.Code == "EquipmentIdNotDerived");
|
||||||
errors.ShouldContain(e => e.Code == "UnsSegmentInvalid");
|
errors.ShouldContain(e => e.Code == "UnsSegmentInvalid");
|
||||||
}
|
}
|
||||||
@@ -432,54 +404,54 @@ public sealed class DraftValidatorTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------
|
||||||
// ValidateAliasTagFullName — Galaxy alias tags must carry TagConfig.FullName
|
// ValidateGalaxyTagFullName — Galaxy equipment tags must carry TagConfig.FullName
|
||||||
// ------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/// <summary>Verifies that an equipment-scoped Tag bound to a GalaxyMxGateway driver whose
|
/// <summary>Verifies that an equipment-scoped Tag bound to a GalaxyMxGateway driver whose
|
||||||
/// TagConfig has no FullName (an alias with no Galaxy reference) is rejected — it would
|
/// TagConfig has no FullName (a Galaxy tag with no Galaxy reference) is rejected — it would
|
||||||
/// subscribe to nothing.</summary>
|
/// subscribe to nothing.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AliasTag_missing_FullName_is_rejected()
|
public void GalaxyTag_missing_FullName_is_rejected()
|
||||||
{
|
{
|
||||||
var draft = new DraftSnapshot
|
var draft = new DraftSnapshot
|
||||||
{
|
{
|
||||||
GenerationId = 1, ClusterId = "c",
|
GenerationId = 1, ClusterId = "c",
|
||||||
DriverInstances = [new DriverInstance { DriverInstanceId = "d-galaxy", ClusterId = "c", NamespaceId = "ns-1", Name = "Galaxy", DriverType = "GalaxyMxGateway", DriverConfig = "{}" }],
|
DriverInstances = [new DriverInstance { DriverInstanceId = "d-galaxy", ClusterId = "c", NamespaceId = "ns-1", Name = "Galaxy", DriverType = "GalaxyMxGateway", DriverConfig = "{}" }],
|
||||||
Tags = [BuildTag(equipmentId: "eq-1", name: "alias", folderPath: null, driverInstanceId: "d-galaxy", tagConfig: "{}")],
|
Tags = [BuildTag(equipmentId: "eq-1", name: "galaxytag", folderPath: null, driverInstanceId: "d-galaxy", tagConfig: "{}")],
|
||||||
};
|
};
|
||||||
|
|
||||||
DraftValidator.Validate(draft).ShouldContain(e => e.Code == "AliasTagMissingReference" && e.Context == "tag-alias");
|
DraftValidator.Validate(draft).ShouldContain(e => e.Code == "GalaxyTagMissingReference" && e.Context == "tag-galaxytag");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that an equipment-scoped Galaxy alias tag carrying a TagConfig.FullName
|
/// <summary>Verifies that an equipment-scoped Galaxy tag carrying a TagConfig.FullName
|
||||||
/// reference is accepted.</summary>
|
/// reference is accepted.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AliasTag_with_FullName_is_accepted()
|
public void GalaxyTag_with_FullName_is_accepted()
|
||||||
{
|
{
|
||||||
var draft = new DraftSnapshot
|
var draft = new DraftSnapshot
|
||||||
{
|
{
|
||||||
GenerationId = 1, ClusterId = "c",
|
GenerationId = 1, ClusterId = "c",
|
||||||
DriverInstances = [new DriverInstance { DriverInstanceId = "d-galaxy", ClusterId = "c", NamespaceId = "ns-1", Name = "Galaxy", DriverType = "GalaxyMxGateway", DriverConfig = "{}" }],
|
DriverInstances = [new DriverInstance { DriverInstanceId = "d-galaxy", ClusterId = "c", NamespaceId = "ns-1", Name = "Galaxy", DriverType = "GalaxyMxGateway", DriverConfig = "{}" }],
|
||||||
Tags = [BuildTag(equipmentId: "eq-1", name: "alias", folderPath: null, driverInstanceId: "d-galaxy", tagConfig: "{\"FullName\":\"X.Y\"}")],
|
Tags = [BuildTag(equipmentId: "eq-1", name: "galaxytag", folderPath: null, driverInstanceId: "d-galaxy", tagConfig: "{\"FullName\":\"X.Y\"}")],
|
||||||
};
|
};
|
||||||
|
|
||||||
DraftValidator.Validate(draft).ShouldNotContain(e => e.Code == "AliasTagMissingReference");
|
DraftValidator.Validate(draft).ShouldNotContain(e => e.Code == "GalaxyTagMissingReference");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that an equipment-scoped Tag bound to a NON-Galaxy driver with an empty
|
/// <summary>Verifies that an equipment-scoped Tag bound to a NON-Galaxy driver with an empty
|
||||||
/// TagConfig is NOT flagged as an alias missing its reference — only GalaxyMxGateway-bound
|
/// TagConfig is NOT flagged as a Galaxy tag missing its reference — only GalaxyMxGateway-bound
|
||||||
/// equipment tags are aliases.</summary>
|
/// equipment tags carry the Galaxy FullName requirement.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AliasTag_check_skips_nonGalaxy_equipment_tag()
|
public void GalaxyTag_check_skips_nonGalaxy_equipment_tag()
|
||||||
{
|
{
|
||||||
var draft = new DraftSnapshot
|
var draft = new DraftSnapshot
|
||||||
{
|
{
|
||||||
GenerationId = 1, ClusterId = "c",
|
GenerationId = 1, ClusterId = "c",
|
||||||
DriverInstances = [new DriverInstance { DriverInstanceId = "d-modbus", ClusterId = "c", NamespaceId = "ns-1", Name = "Modbus", DriverType = "Modbus", DriverConfig = "{}" }],
|
DriverInstances = [new DriverInstance { DriverInstanceId = "d-modbus", ClusterId = "c", NamespaceId = "ns-1", Name = "Modbus", DriverType = "Modbus", DriverConfig = "{}" }],
|
||||||
Tags = [BuildTag(equipmentId: "eq-1", name: "alias", folderPath: null, driverInstanceId: "d-modbus", tagConfig: "{}")],
|
Tags = [BuildTag(equipmentId: "eq-1", name: "galaxytag", folderPath: null, driverInstanceId: "d-modbus", tagConfig: "{}")],
|
||||||
};
|
};
|
||||||
|
|
||||||
DraftValidator.Validate(draft).ShouldNotContain(e => e.Code == "AliasTagMissingReference");
|
DraftValidator.Validate(draft).ShouldNotContain(e => e.Code == "GalaxyTagMissingReference");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user