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 { /// /// 01/U-6 knob-inertness guard (OVERALL theme #1): the public property set of /// must be EXACTLY the pipeline-wired knobs. Every option a /// parser can populate must map to a strategy the builder actually composes — a parsed-but-unapplied /// knob (the bulkhead genre this pass deleted) is inertness the interface-forwarding and /// unwrapped-dispatch guards can't catch. To add a knob: wire it in DriverResiliencePipelineBuilder, /// add a behavior test that proves it engages, THEN add it to the expected set below. /// (RecycleIntervalSeconds is itself Tier-C-dormant per 01/U-2 — a documented open question, /// explicitly out of this pass's scope; it stays in the expected list as the recycle scheduler, not /// the Polly pipeline, is its consumer.) /// [Fact] public void Options_properties_are_exactly_the_pipeline_wired_set() { var expected = new[] { "Tier", "CapabilityPolicies", "RecycleIntervalSeconds" }; var actual = typeof(DriverResilienceOptions) .GetProperties() .Select(p => p.Name) .OrderBy(n => n) .ToArray(); actual.ShouldBe(expected.OrderBy(n => n).ToArray(), "DriverResilienceOptions must expose only pipeline-wired knobs — a new option needs a builder " + "strategy + a behavior test before it is added to the expected set (guards against the deleted " + "bulkhead 'parsed-but-unapplied' genre; see 01/U-6)."); } /// Verifies that tier defaults cover every capability. /// The driver tier to test. [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); } /// Verifies that write never retries by default. /// The driver tier to test. [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); } /// Verifies that alarm acknowledge never retries by default. /// The driver tier to test. [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); } /// Verifies that idempotent capabilities retry by default. /// The driver tier to test. /// The driver capability to test. [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); } /// Verifies that TierC disables circuit breaker deferring to supervisor. [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)"); } /// Verifies that TierA and TierB enable circuit breaker. /// The driver tier to test. [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); } /// Verifies that resolve uses tier defaults when no override is set. [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]); } /// Verifies that resolve uses override when configured. [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 . /// /// The driver tier to test. [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)); } }