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 DriverResilienceOptionsTests { [Theory] [InlineData(DriverTier.A)] [InlineData(DriverTier.B)] [InlineData(DriverTier.C)] public void TierDefaults_Cover_EveryCapability(DriverTier tier) { var defaults = DriverResilienceOptions.GetTierDefaults(tier); foreach (var capability in Enum.GetValues()) defaults.ShouldContainKey(capability); } [Theory] [InlineData(DriverTier.A)] [InlineData(DriverTier.B)] [InlineData(DriverTier.C)] public void Write_NeverRetries_ByDefault(DriverTier tier) { var defaults = DriverResilienceOptions.GetTierDefaults(tier); defaults[DriverCapability.Write].RetryCount.ShouldBe(0); } [Theory] [InlineData(DriverTier.A)] [InlineData(DriverTier.B)] [InlineData(DriverTier.C)] public void AlarmAcknowledge_NeverRetries_ByDefault(DriverTier tier) { var defaults = DriverResilienceOptions.GetTierDefaults(tier); defaults[DriverCapability.AlarmAcknowledge].RetryCount.ShouldBe(0); } [Theory] [InlineData(DriverTier.A, DriverCapability.Read)] [InlineData(DriverTier.A, DriverCapability.HistoryRead)] [InlineData(DriverTier.B, DriverCapability.Discover)] [InlineData(DriverTier.B, DriverCapability.Probe)] [InlineData(DriverTier.C, DriverCapability.AlarmSubscribe)] public void IdempotentCapabilities_Retry_ByDefault(DriverTier tier, DriverCapability capability) { var defaults = DriverResilienceOptions.GetTierDefaults(tier); defaults[capability].RetryCount.ShouldBeGreaterThan(0); } [Fact] public void TierC_DisablesCircuitBreaker_DeferringToSupervisor() { var defaults = DriverResilienceOptions.GetTierDefaults(DriverTier.C); foreach (var (_, policy) in defaults) policy.BreakerFailureThreshold.ShouldBe(0, "Tier C breaker is handled by the Proxy supervisor (decision #68)"); } [Theory] [InlineData(DriverTier.A)] [InlineData(DriverTier.B)] public void TierAAndB_EnableCircuitBreaker(DriverTier tier) { var defaults = DriverResilienceOptions.GetTierDefaults(tier); foreach (var (_, policy) in defaults) policy.BreakerFailureThreshold.ShouldBeGreaterThan(0); } [Fact] public void Resolve_Uses_TierDefaults_When_NoOverride() { var options = new DriverResilienceOptions { Tier = DriverTier.A }; var resolved = options.Resolve(DriverCapability.Read); resolved.ShouldBe(DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]); } [Fact] public void Resolve_Uses_Override_When_Configured() { var custom = new CapabilityPolicy(TimeoutSeconds: 42, RetryCount: 7, BreakerFailureThreshold: 9); var options = new DriverResilienceOptions { Tier = DriverTier.A, CapabilityPolicies = new Dictionary { [DriverCapability.Read] = custom, }, }; options.Resolve(DriverCapability.Read).ShouldBe(custom); options.Resolve(DriverCapability.Write).ShouldBe( DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Write]); } /// /// Core-010 regression: every value must successfully resolve /// under every tier with a default . A future /// enum-only addition that forgets to update GetTierDefaults would otherwise blow up /// on the hot path with . /// [Theory] [InlineData(DriverTier.A)] [InlineData(DriverTier.B)] [InlineData(DriverTier.C)] public void Resolve_Returns_NonNull_Policy_For_Every_Capability(DriverTier tier) { var options = new DriverResilienceOptions { Tier = tier }; foreach (var capability in Enum.GetValues()) { var policy = options.Resolve(capability); policy.ShouldNotBeNull( $"every DriverCapability must resolve to a non-null policy for tier {tier} — {capability} did not"); } } /// /// Core-010 regression: when a capability is somehow missing from BOTH the override /// map and the tier defaults (defensive — should be impossible thanks to the /// TierDefaults_Cover_EveryCapability invariant, but is the failure mode the /// finding flagged), Resolve must throw a diagnostic /// that names the missing capability and tier — not a bare lookup failure. /// [Fact] public void Resolve_Throws_Diagnostic_When_Capability_Missing_From_Tier_Defaults() { // Use a CapabilityPolicies dict that purposely omits one capability and use reflection // to confirm the message names the capability when the tier defaults also omit it. // We can't easily mutate GetTierDefaults so we exercise the documented behavior on a // synthetic non-tier-known capability (we cast an out-of-range enum value). var options = new DriverResilienceOptions { Tier = DriverTier.A }; var bogus = (DriverCapability)int.MaxValue; var ex = Should.Throw(() => options.Resolve(bogus)); ex.Message.ShouldContain(bogus.ToString()); ex.Message.ShouldContain(DriverTier.A.ToString()); ex.Message.ShouldContain(nameof(DriverResilienceOptions.GetTierDefaults)); } }