diff --git a/DevProxy.Integration.Tests/BehaviorPluginsIntegrationTests.cs b/DevProxy.Integration.Tests/BehaviorPluginsIntegrationTests.cs index aa39cf27..ee9d67c5 100644 --- a/DevProxy.Integration.Tests/BehaviorPluginsIntegrationTests.cs +++ b/DevProxy.Integration.Tests/BehaviorPluginsIntegrationTests.cs @@ -66,6 +66,46 @@ public async Task GenericRandomError_Rate100_OverridesOriginWithConfiguredError( Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode); } + [Fact] + public async Task GenericRandomError_Rate99_SometimesPassesThrough() + { + await using var origin = await FakeOrigin.StartAsync(); + var urls = KestrelProxyHarness.BuildUrlsToWatch(origin.Host); + + // rate:99 ⇒ ~1% of requests pass through. Previously rate:99 behaved like rate:100. + var config = PluginConfig.FromJson($$""" + { + "rate": 99, + "errors": [ + { + "request": { "url": "http://{{origin.Host}}/*" }, + "responses": [ { "statusCode": 503 } ] + } + ] + } + """); + var plugin = new GenericRandomErrorPlugin( + SharedHttpClient, + NullLogger.Instance, + urls, + ProxyConfig, + config); + + await using var proxy = await KestrelProxyHarness.StartAsync( + origin.Host, [plugin]); + using var client = proxy.CreateHttpClient(); + + // Probability of 2000 consecutive failures at 99% is ~2e-9. + var passedThrough = false; + for (var i = 0; i < 2000 && !passedThrough; i++) + { + using var response = await client.GetAsync(new Uri($"http://{origin.Host}/get")); + passedThrough = response.StatusCode == HttpStatusCode.OK; + } + + Assert.True(passedThrough); + } + [Fact] public async Task GenericRandomError_DynamicRetryAfterOnNon429_IsResolved() { diff --git a/DevProxy.Plugins/Behavior/GenericRandomErrorPlugin.cs b/DevProxy.Plugins/Behavior/GenericRandomErrorPlugin.cs index d0ff8a8d..6e4d8c17 100644 --- a/DevProxy.Plugins/Behavior/GenericRandomErrorPlugin.cs +++ b/DevProxy.Plugins/Behavior/GenericRandomErrorPlugin.cs @@ -138,7 +138,7 @@ public override Task BeforeRequestAsync(ProxyRequestArgs e, CancellationToken ca } // uses config to determine if a request should be failed - private GenericRandomErrorFailMode ShouldFail() => _random.Next(1, 100) <= Configuration.Rate ? GenericRandomErrorFailMode.Random : GenericRandomErrorFailMode.PassThru; + private GenericRandomErrorFailMode ShouldFail() => _random.Next(0, 100) < Configuration.Rate ? GenericRandomErrorFailMode.Random : GenericRandomErrorFailMode.PassThru; private void FailResponse(ProxyRequestArgs e) { diff --git a/DevProxy.Plugins/Behavior/GraphRandomErrorPlugin.cs b/DevProxy.Plugins/Behavior/GraphRandomErrorPlugin.cs index 1e69d29e..1af35fa0 100644 --- a/DevProxy.Plugins/Behavior/GraphRandomErrorPlugin.cs +++ b/DevProxy.Plugins/Behavior/GraphRandomErrorPlugin.cs @@ -206,7 +206,7 @@ public override Task BeforeRequestAsync(ProxyRequestArgs e, CancellationToken ca } // uses config to determine if a request should be failed - private GraphRandomErrorFailMode ShouldFail() => _random.Next(1, 100) <= Configuration.Rate ? GraphRandomErrorFailMode.Random : GraphRandomErrorFailMode.PassThru; + private GraphRandomErrorFailMode ShouldFail() => _random.Next(0, 100) < Configuration.Rate ? GraphRandomErrorFailMode.Random : GraphRandomErrorFailMode.PassThru; private void FailResponse(ProxyRequestArgs e) {