64e3fbe035
v2-ci / build (push) Failing after 1m43s
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
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
226 lines
8.3 KiB
C#
226 lines
8.3 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Resilience;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class DriverResilienceOptionsParserTests
|
|
{
|
|
/// <summary>Verifies that null JSON returns pure tier defaults.</summary>
|
|
[Fact]
|
|
public void NullJson_ReturnsPureTierDefaults()
|
|
{
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, null, out var diag);
|
|
|
|
diag.ShouldBeNull();
|
|
options.Tier.ShouldBe(DriverTier.A);
|
|
options.Resolve(DriverCapability.Read).ShouldBe(
|
|
DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]);
|
|
}
|
|
|
|
/// <summary>Verifies that whitespace JSON returns defaults.</summary>
|
|
[Fact]
|
|
public void WhitespaceJson_ReturnsDefaults()
|
|
{
|
|
DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.B, " ", out var diag);
|
|
diag.ShouldBeNull();
|
|
}
|
|
|
|
/// <summary>Verifies that malformed JSON falls back with diagnostic.</summary>
|
|
[Fact]
|
|
public void MalformedJson_FallsBack_WithDiagnostic()
|
|
{
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, "{not json", out var diag);
|
|
|
|
diag.ShouldNotBeNull();
|
|
diag.ShouldContain("malformed");
|
|
options.Tier.ShouldBe(DriverTier.A);
|
|
options.Resolve(DriverCapability.Read).ShouldBe(
|
|
DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]);
|
|
}
|
|
|
|
/// <summary>Verifies that empty object returns defaults.</summary>
|
|
[Fact]
|
|
public void EmptyObject_ReturnsDefaults()
|
|
{
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, "{}", out var diag);
|
|
|
|
diag.ShouldBeNull();
|
|
options.Resolve(DriverCapability.Write).ShouldBe(
|
|
DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Write]);
|
|
}
|
|
|
|
/// <summary>Verifies that Read override is merged into tier defaults.</summary>
|
|
[Fact]
|
|
public void ReadOverride_MergedIntoTierDefaults()
|
|
{
|
|
var json = """
|
|
{
|
|
"capabilityPolicies": {
|
|
"Read": { "timeoutSeconds": 5, "retryCount": 7, "breakerFailureThreshold": 2 }
|
|
}
|
|
}
|
|
""";
|
|
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out var diag);
|
|
|
|
diag.ShouldBeNull();
|
|
var read = options.Resolve(DriverCapability.Read);
|
|
read.TimeoutSeconds.ShouldBe(5);
|
|
read.RetryCount.ShouldBe(7);
|
|
read.BreakerFailureThreshold.ShouldBe(2);
|
|
|
|
// Other capabilities untouched
|
|
options.Resolve(DriverCapability.Write).ShouldBe(
|
|
DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Write]);
|
|
}
|
|
|
|
/// <summary>Verifies that partial policy fills missing fields from tier default.</summary>
|
|
[Fact]
|
|
public void PartialPolicy_FillsMissingFieldsFromTierDefault()
|
|
{
|
|
var json = """
|
|
{
|
|
"capabilityPolicies": {
|
|
"Read": { "retryCount": 10 }
|
|
}
|
|
}
|
|
""";
|
|
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out _);
|
|
|
|
var read = options.Resolve(DriverCapability.Read);
|
|
var tierDefault = DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read];
|
|
read.RetryCount.ShouldBe(10);
|
|
read.TimeoutSeconds.ShouldBe(tierDefault.TimeoutSeconds, "partial override; timeout falls back to tier default");
|
|
read.BreakerFailureThreshold.ShouldBe(tierDefault.BreakerFailureThreshold);
|
|
}
|
|
|
|
/// <summary>Verifies that bulkhead overrides are honored.</summary>
|
|
[Fact]
|
|
public void BulkheadOverrides_AreHonored()
|
|
{
|
|
var json = """
|
|
{ "bulkheadMaxConcurrent": 100, "bulkheadMaxQueue": 500 }
|
|
""";
|
|
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.B, json, out _);
|
|
|
|
options.BulkheadMaxConcurrent.ShouldBe(100);
|
|
options.BulkheadMaxQueue.ShouldBe(500);
|
|
}
|
|
|
|
/// <summary>Verifies that unknown capability surfaces in diagnostic but does not fail.</summary>
|
|
[Fact]
|
|
public void UnknownCapability_Surfaces_InDiagnostic_ButDoesNotFail()
|
|
{
|
|
var json = """
|
|
{
|
|
"capabilityPolicies": {
|
|
"InventedCapability": { "timeoutSeconds": 99 }
|
|
}
|
|
}
|
|
""";
|
|
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out var diag);
|
|
|
|
diag.ShouldNotBeNull();
|
|
diag.ShouldContain("InventedCapability");
|
|
// Known capabilities untouched.
|
|
options.Resolve(DriverCapability.Read).ShouldBe(
|
|
DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]);
|
|
}
|
|
|
|
/// <summary>Verifies that property names are case insensitive.</summary>
|
|
[Fact]
|
|
public void PropertyNames_AreCaseInsensitive()
|
|
{
|
|
var json = """
|
|
{ "BULKHEADMAXCONCURRENT": 42 }
|
|
""";
|
|
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out _);
|
|
|
|
options.BulkheadMaxConcurrent.ShouldBe(42);
|
|
}
|
|
|
|
/// <summary>Verifies that capability name is case insensitive.</summary>
|
|
[Fact]
|
|
public void CapabilityName_IsCaseInsensitive()
|
|
{
|
|
var json = """
|
|
{ "capabilityPolicies": { "read": { "retryCount": 99 } } }
|
|
""";
|
|
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out var diag);
|
|
|
|
diag.ShouldBeNull();
|
|
options.Resolve(DriverCapability.Read).RetryCount.ShouldBe(99);
|
|
}
|
|
|
|
/// <summary>Verifies that every tier with empty JSON round-trips its defaults.</summary>
|
|
/// <param name="tier">The driver tier to test.</param>
|
|
[Theory]
|
|
[InlineData(DriverTier.A)]
|
|
[InlineData(DriverTier.B)]
|
|
[InlineData(DriverTier.C)]
|
|
public void EveryTier_WithEmptyJson_RoundTrips_Its_Defaults(DriverTier tier)
|
|
{
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(tier, "{}", out var diag);
|
|
|
|
diag.ShouldBeNull();
|
|
options.Tier.ShouldBe(tier);
|
|
foreach (var cap in Enum.GetValues<DriverCapability>())
|
|
options.Resolve(cap).ShouldBe(DriverResilienceOptions.GetTierDefaults(tier)[cap]);
|
|
}
|
|
|
|
/// <summary>Verifies that RecycleIntervalSeconds on Tier C with positive value parses and surfaces.</summary>
|
|
[Fact]
|
|
public void RecycleIntervalSeconds_TierC_PositiveValue_ParsesAndSurfaces()
|
|
{
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(
|
|
DriverTier.C, "{\"recycleIntervalSeconds\":3600}", out var diag);
|
|
|
|
diag.ShouldBeNull();
|
|
options.RecycleIntervalSeconds.ShouldBe(3600);
|
|
}
|
|
|
|
/// <summary>Verifies that RecycleIntervalSeconds when null defaults to null.</summary>
|
|
[Fact]
|
|
public void RecycleIntervalSeconds_Null_DefaultsToNull()
|
|
{
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.C, "{}", out _);
|
|
options.RecycleIntervalSeconds.ShouldBeNull();
|
|
}
|
|
|
|
/// <summary>Verifies that RecycleIntervalSeconds on Tier A or B is rejected with diagnostic.</summary>
|
|
/// <param name="tier">The driver tier to test.</param>
|
|
[Theory]
|
|
[InlineData(DriverTier.A)]
|
|
[InlineData(DriverTier.B)]
|
|
public void RecycleIntervalSeconds_OnTierAorB_Rejected_With_Diagnostic(DriverTier tier)
|
|
{
|
|
// Decision #74 — in-process drivers must not scheduled-recycle because it would
|
|
// tear down every OPC UA session. The parser surfaces a diagnostic rather than
|
|
// silently honouring the value.
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(
|
|
tier, "{\"recycleIntervalSeconds\":3600}", out var diag);
|
|
|
|
options.RecycleIntervalSeconds.ShouldBeNull();
|
|
diag.ShouldContain("Tier C only");
|
|
}
|
|
|
|
/// <summary>Verifies that RecycleIntervalSeconds with non-positive value is rejected with diagnostic.</summary>
|
|
[Fact]
|
|
public void RecycleIntervalSeconds_NonPositive_Rejected_With_Diagnostic()
|
|
{
|
|
var options = DriverResilienceOptionsParser.ParseOrDefaults(
|
|
DriverTier.C, "{\"recycleIntervalSeconds\":0}", out var diag);
|
|
|
|
options.RecycleIntervalSeconds.ShouldBeNull();
|
|
diag.ShouldContain("must be positive");
|
|
}
|
|
}
|