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 { [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]); } [Fact] public void WhitespaceJson_ReturnsDefaults() { DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.B, " ", out var diag); diag.ShouldBeNull(); } [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]); } [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]); } [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]); } [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); } [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); } [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]); } [Fact] public void PropertyNames_AreCaseInsensitive() { var json = """ { "BULKHEADMAXCONCURRENT": 42 } """; var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out _); options.BulkheadMaxConcurrent.ShouldBe(42); } [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); } [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()) options.Resolve(cap).ShouldBe(DriverResilienceOptions.GetTierDefaults(tier)[cap]); } }