From b53221e44a014998385a827b15035f370d38a11e Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 12 May 2026 00:50:28 -0400 Subject: [PATCH] test(commons): failing tests for OpcUaEndpointConfigValidator --- .../OpcUaEndpointConfigValidatorTests.cs | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 tests/ScadaLink.Commons.Tests/Validators/OpcUaEndpointConfigValidatorTests.cs diff --git a/tests/ScadaLink.Commons.Tests/Validators/OpcUaEndpointConfigValidatorTests.cs b/tests/ScadaLink.Commons.Tests/Validators/OpcUaEndpointConfigValidatorTests.cs new file mode 100644 index 0000000..023704b --- /dev/null +++ b/tests/ScadaLink.Commons.Tests/Validators/OpcUaEndpointConfigValidatorTests.cs @@ -0,0 +1,112 @@ +using ScadaLink.Commons.Types.DataConnections; +using ScadaLink.Commons.Types.Flattening; +using ScadaLink.Commons.Validators; + +namespace ScadaLink.Commons.Tests.Validators; + +public class OpcUaEndpointConfigValidatorTests +{ + private static OpcUaEndpointConfig Valid() => new() + { + EndpointUrl = "opc.tcp://plant-a:4840", + // Defaults satisfy the spec: Lifetime(30) >= 3 * KeepAlive(10). + }; + + [Fact] + public void Validate_DefaultsWithValidUrl_IsValid() + { + var result = OpcUaEndpointConfigValidator.Validate(Valid()); + Assert.True(result.IsValid); + Assert.Empty(result.Errors); + } + + [Fact] + public void Validate_MissingEndpointUrl_Fails() + { + var c = Valid(); + c.EndpointUrl = ""; + var r = OpcUaEndpointConfigValidator.Validate(c); + Assert.False(r.IsValid); + Assert.Contains(r.Errors, e => + e.EntityName == "EndpointUrl" + && e.Category == ValidationCategory.ConnectionConfig + && e.Message.Contains("required", StringComparison.OrdinalIgnoreCase)); + } + + [Theory] + [InlineData("http://x")] + [InlineData("opc.tcp://")] + [InlineData("not a url")] + public void Validate_BadEndpointUrl_Fails(string url) + { + var c = Valid(); + c.EndpointUrl = url; + var r = OpcUaEndpointConfigValidator.Validate(c); + Assert.False(r.IsValid); + Assert.Contains(r.Errors, e => e.EntityName == "EndpointUrl"); + } + + [Fact] + public void Validate_LifetimeLessThanThreeTimesKeepAlive_Fails() + { + var c = Valid(); + c.KeepAliveCount = 10; + c.LifetimeCount = 29; // 3*10 = 30; 29 < 30 → invalid + var r = OpcUaEndpointConfigValidator.Validate(c); + Assert.False(r.IsValid); + Assert.Contains(r.Errors, e => e.EntityName == "LifetimeCount"); + } + + [Theory] + [InlineData(nameof(OpcUaEndpointConfig.SessionTimeoutMs))] + [InlineData(nameof(OpcUaEndpointConfig.OperationTimeoutMs))] + [InlineData(nameof(OpcUaEndpointConfig.PublishingIntervalMs))] + [InlineData(nameof(OpcUaEndpointConfig.SamplingIntervalMs))] + public void Validate_NonPositiveTiming_Fails(string field) + { + var c = Valid(); + typeof(OpcUaEndpointConfig).GetProperty(field)!.SetValue(c, 0); + var r = OpcUaEndpointConfigValidator.Validate(c); + Assert.False(r.IsValid); + Assert.Contains(r.Errors, e => e.EntityName == field); + } + + [Fact] + public void Validate_QueueSizeZero_Fails() + { + var c = Valid(); + c.QueueSize = 0; + var r = OpcUaEndpointConfigValidator.Validate(c); + Assert.Contains(r.Errors, e => e.EntityName == "QueueSize"); + } + + [Fact] + public void Validate_HeartbeatEnabledNoTagPath_Fails() + { + var c = Valid(); + c.Heartbeat = new OpcUaHeartbeatConfig { TagPath = "", MaxSilenceSeconds = 30 }; + var r = OpcUaEndpointConfigValidator.Validate(c); + Assert.Contains(r.Errors, e => e.EntityName == "Heartbeat.TagPath"); + } + + [Fact] + public void Validate_HeartbeatNonPositiveSilence_Fails() + { + var c = Valid(); + c.Heartbeat = new OpcUaHeartbeatConfig { TagPath = "Hb", MaxSilenceSeconds = 0 }; + var r = OpcUaEndpointConfigValidator.Validate(c); + Assert.Contains(r.Errors, e => e.EntityName == "Heartbeat.MaxSilenceSeconds"); + } + + [Fact] + public void Validate_FieldPrefix_AppliedToEveryError() + { + var c = Valid(); + c.EndpointUrl = ""; + c.QueueSize = 0; + var r = OpcUaEndpointConfigValidator.Validate(c, fieldPrefix: "Primary."); + Assert.All(r.Errors, e => Assert.StartsWith("Primary.", e.EntityName!)); + Assert.Contains(r.Errors, e => e.EntityName == "Primary.EndpointUrl"); + Assert.Contains(r.Errors, e => e.EntityName == "Primary.QueueSize"); + } +}