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; /// /// Load-bearing regression guard for 01/S-6 — an operator-authorable out-of-range /// ResilienceConfig value must NEVER brick a healthy driver. Before the R2-02 hardening pass, /// "timeoutSeconds": 0 (→ TimeSpan.Zero, outside Polly's [10 ms, 24 h] window) and /// "breakerFailureThreshold": 1 (below Polly's MinimumThroughput floor of 2) each made /// throw ValidationException inside the /// memoizing GetOrAdd factory on EVERY capability call for that (instance, host, /// capability) — a permanent brick while the driver is healthy. The parser now clamps and the /// builder belt-and-braces clamps, so a pipeline built from a parsed hostile config executes. /// [Trait("Category", "Unit")] public sealed class ResilienceConfigBrickTests { /// A parsed timeoutSeconds:0 override must not brick a capability call. [Fact] public async Task ParsedConfig_WithZeroTimeout_DoesNotBrickCapabilityCalls() { var options = DriverResilienceOptionsParser.ParseOrDefaults( DriverTier.A, "{\"capabilityPolicies\":{\"Read\":{\"timeoutSeconds\":0}}}", out _); var pipeline = new DriverResiliencePipelineBuilder() .GetOrCreate("i1", "h1", DriverCapability.Read, options); var result = await pipeline.ExecuteAsync(_ => ValueTask.FromResult(42), CancellationToken.None); result.ShouldBe(42); } /// A parsed breakerFailureThreshold:1 override must not brick a capability call. [Fact] public async Task ParsedConfig_WithBreakerThresholdOne_DoesNotBrickCapabilityCalls() { var options = DriverResilienceOptionsParser.ParseOrDefaults( DriverTier.A, "{\"capabilityPolicies\":{\"Read\":{\"breakerFailureThreshold\":1}}}", out _); var pipeline = new DriverResiliencePipelineBuilder() .GetOrCreate("i1", "h1", DriverCapability.Read, options); var result = await pipeline.ExecuteAsync(_ => ValueTask.FromResult(42), CancellationToken.None); result.ShouldBe(42); } }