feat(uns): validate typed TagConfig before save (F-uns-2 / #156)
v2-ci / build (push) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
The per-driver editor models expose Validate() (required-field checks) but the TagModal never called them, so a blank required field (e.g. S7 address, AbCip tag path) saved silently and only failed at deploy/connect. Add a TagConfigValidator registry (DriverType -> model.FromJson(json).Validate(), parallel to TagConfigEditorMap) and call it in SaveAsync before the service call — a non-null result sets the modal error and blocks save. Unmapped drivers (no typed editor) and Modbus (no required field) return null. Editors untouched. AdminUI.Tests 307/307 (12 new validator tests); build clean.
This commit is contained in:
@@ -197,6 +197,15 @@
|
||||
_error = null;
|
||||
try
|
||||
{
|
||||
// Client-side per-driver config validation (the typed editor's Validate()), so a blank
|
||||
// required field is caught here rather than silently saving and failing at deploy/connect.
|
||||
var configError = TagConfigValidator.Validate(SelectedDriverType, _form.TagConfig);
|
||||
if (configError is not null)
|
||||
{
|
||||
_error = configError;
|
||||
return;
|
||||
}
|
||||
|
||||
var input = new TagInput(
|
||||
_form.TagId,
|
||||
_form.Name,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Client-side TagConfig validation dispatched by <c>DriverType</c> (parallel to
|
||||
/// <see cref="TagConfigEditorMap"/>). Each entry parses the tag's config JSON into the driver's typed
|
||||
/// model and runs its <c>Validate()</c>. Returns an error string to block the TagModal save, or null
|
||||
/// when the config is valid — or when no typed validator is registered (unmapped drivers use the raw
|
||||
/// textarea and are validated server-side at deploy).
|
||||
/// </summary>
|
||||
public static class TagConfigValidator
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, Func<string?, string?>> Validators =
|
||||
new Dictionary<string, Func<string?, string?>>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["ModbusTcp"] = j => ModbusTagConfigModel.FromJson(j).Validate(),
|
||||
["S7"] = j => S7TagConfigModel.FromJson(j).Validate(),
|
||||
["AbCip"] = j => AbCipTagConfigModel.FromJson(j).Validate(),
|
||||
["AbLegacy"] = j => AbLegacyTagConfigModel.FromJson(j).Validate(),
|
||||
["TwinCat"] = j => TwinCATTagConfigModel.FromJson(j).Validate(),
|
||||
["Focas"] = j => FocasTagConfigModel.FromJson(j).Validate(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Validates a tag's <paramref name="configJson"/> for the given <paramref name="driverType"/>.
|
||||
/// Returns an error string to block save, or null when valid / no typed validator is registered.
|
||||
/// </summary>
|
||||
public static string? Validate(string? driverType, string? configJson)
|
||||
=> driverType is not null && Validators.TryGetValue(driverType, out var v) ? v(configJson) : null;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Shouldly;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
||||
|
||||
public sealed class TagConfigValidatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void No_driver_type_is_valid()
|
||||
=> TagConfigValidator.Validate(null, "{}").ShouldBeNull();
|
||||
|
||||
[Fact]
|
||||
public void Unmapped_driver_has_no_typed_validator_so_is_valid()
|
||||
=> TagConfigValidator.Validate("OpcUaClient", "{}").ShouldBeNull();
|
||||
|
||||
[Fact]
|
||||
public void Modbus_has_no_required_field_so_empty_config_is_valid()
|
||||
=> TagConfigValidator.Validate("ModbusTcp", "{}").ShouldBeNull();
|
||||
|
||||
// Drivers whose Validate() requires a field: blank config must return an error string.
|
||||
[Theory]
|
||||
[InlineData("S7")]
|
||||
[InlineData("AbCip")]
|
||||
[InlineData("AbLegacy")]
|
||||
[InlineData("TwinCat")]
|
||||
[InlineData("Focas")]
|
||||
public void Required_field_blank_is_rejected(string driverType)
|
||||
{
|
||||
TagConfigValidator.Validate(driverType, "{}").ShouldNotBeNullOrEmpty();
|
||||
TagConfigValidator.Validate(driverType, null).ShouldNotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void S7_with_address_is_valid()
|
||||
=> TagConfigValidator.Validate("S7", """{"address":"DB1.DBW0"}""").ShouldBeNull();
|
||||
|
||||
[Fact]
|
||||
public void AbCip_with_tag_path_is_valid()
|
||||
=> TagConfigValidator.Validate("AbCip", """{"tagPath":"Program:Main.Speed"}""").ShouldBeNull();
|
||||
|
||||
[Fact]
|
||||
public void TwinCat_with_symbol_path_is_valid()
|
||||
=> TagConfigValidator.Validate("TwinCat", """{"symbolPath":"MAIN.bStart"}""").ShouldBeNull();
|
||||
|
||||
[Fact]
|
||||
public void Driver_type_lookup_is_case_insensitive()
|
||||
=> TagConfigValidator.Validate("s7", "{}").ShouldNotBeNullOrEmpty();
|
||||
}
|
||||
Reference in New Issue
Block a user