-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce Fallback Resilience Strategy (#1158)
- Loading branch information
Showing
17 changed files
with
903 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
using Polly.Fallback; | ||
using Polly.Strategy; | ||
|
||
namespace Polly.Core.Tests.Fallback; | ||
|
||
public class FallbackHandlerTests | ||
{ | ||
[Fact] | ||
public void SetFallback_ConfigureAsInvalid_Throws() | ||
{ | ||
var handler = new FallbackHandler(); | ||
|
||
handler | ||
.Invoking(h => h.SetFallback<int>(handler => | ||
{ | ||
handler.FallbackAction = null!; | ||
handler.ShouldHandle = null!; | ||
})) | ||
.Should() | ||
.Throw<ValidationException>() | ||
.WithMessage(""" | ||
The fallback handler configuration is invalid. | ||
|
||
Validation Errors: | ||
The ShouldHandle field is required. | ||
The FallbackAction field is required. | ||
"""); | ||
} | ||
|
||
[Fact] | ||
public void SetVoidFallback_ConfigureAsInvalid_Throws() | ||
{ | ||
var handler = new FallbackHandler(); | ||
|
||
handler | ||
.Invoking(h => h.SetVoidFallback(handler => | ||
{ | ||
handler.FallbackAction = null!; | ||
handler.ShouldHandle = null!; | ||
})) | ||
.Should() | ||
.Throw<ValidationException>() | ||
.WithMessage(""" | ||
The fallback handler configuration is invalid. | ||
|
||
Validation Errors: | ||
The ShouldHandle field is required. | ||
The FallbackAction field is required. | ||
"""); | ||
} | ||
|
||
[Fact] | ||
public void SetFallback_Empty_Discarded() | ||
{ | ||
var handler = new FallbackHandler() | ||
.SetFallback<int>(handler => | ||
{ | ||
handler.FallbackAction = (_, _) => new ValueTask<int>(0); | ||
}) | ||
.SetVoidFallback(handler => | ||
{ | ||
handler.FallbackAction = (_, _) => default; | ||
}); | ||
|
||
handler.IsEmpty.Should().BeTrue(); | ||
handler.CreateHandler().Should().BeNull(); | ||
} | ||
|
||
[Fact] | ||
public async Task SetFallback_Ok() | ||
{ | ||
var handler = new FallbackHandler() | ||
.SetFallback<int>(handler => | ||
{ | ||
handler.FallbackAction = (_, _) => new ValueTask<int>(0); | ||
handler.ShouldHandle.HandleResult(-1); | ||
}) | ||
.CreateHandler(); | ||
|
||
var args = new HandleFallbackArguments(ResilienceContext.Get()); | ||
handler.Should().NotBeNull(); | ||
var action = await handler!.ShouldHandleAsync(new Outcome<int>(-1), args); | ||
(await action!(new Outcome<int>(-1), args)).Should().Be(0); | ||
|
||
action = await handler!.ShouldHandleAsync(new Outcome<int>(0), args); | ||
action.Should().BeNull(); | ||
} | ||
|
||
[Fact] | ||
public async Task SetVoidFallback_Ok() | ||
{ | ||
var handler = new FallbackHandler() | ||
.SetVoidFallback(handler => | ||
{ | ||
handler.FallbackAction = (_, _) => default; | ||
handler.ShouldHandle.HandleException<InvalidOperationException>(); | ||
}) | ||
.CreateHandler(); | ||
|
||
var args = new HandleFallbackArguments(ResilienceContext.Get()); | ||
handler.Should().NotBeNull(); | ||
var action = await handler!.ShouldHandleAsync(new Outcome<VoidResult>(new InvalidOperationException()), args); | ||
action.Should().NotBeNull(); | ||
(await action!(new Outcome<VoidResult>(new InvalidOperationException()), args)).Should().Be(VoidResult.Instance); | ||
|
||
action = await handler!.ShouldHandleAsync(new Outcome<VoidResult>(new ArgumentNullException()), args); | ||
action.Should().BeNull(); | ||
} | ||
|
||
[Fact] | ||
public async Task ShouldHandleAsync_UnknownResultType_Null() | ||
{ | ||
var handler = new FallbackHandler() | ||
.SetFallback<int>(handler => | ||
{ | ||
handler.FallbackAction = (_, _) => default; | ||
handler.ShouldHandle.HandleException<InvalidOperationException>(); | ||
}) | ||
.SetFallback<string>(handler => | ||
{ | ||
handler.FallbackAction = (_, _) => default; | ||
}) | ||
.CreateHandler(); | ||
|
||
var args = new HandleFallbackArguments(ResilienceContext.Get()); | ||
var action = await handler!.ShouldHandleAsync(new Outcome<double>(new InvalidOperationException()), args); | ||
action.Should().BeNull(); | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
src/Polly.Core.Tests/Fallback/FallbackResilienceStrategyBuilderExtensionsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using System; | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Threading.Tasks; | ||
using Polly.Fallback; | ||
|
||
namespace Polly.Core.Tests.Fallback; | ||
|
||
public class FallbackResilienceStrategyBuilderExtensionsTests | ||
{ | ||
private readonly ResilienceStrategyBuilder _builder = new(); | ||
|
||
public static readonly TheoryData<Action<ResilienceStrategyBuilder>> FallbackCases = new() | ||
{ | ||
builder => | ||
{ | ||
builder.AddFallback(new FallbackStrategyOptions()); | ||
}, | ||
builder => | ||
{ | ||
builder.AddFallback(new FallbackStrategyOptions<double>{ FallbackAction = (_, _) => new ValueTask<double>(0) }); | ||
}, | ||
builder => | ||
{ | ||
builder.AddFallback<double>(handle => { }, (_, _) => new ValueTask<double>(0)); | ||
}, | ||
}; | ||
|
||
[MemberData(nameof(FallbackCases))] | ||
[Theory] | ||
public void AddFallback_Ok(Action<ResilienceStrategyBuilder> configure) | ||
{ | ||
configure(_builder); | ||
_builder.Build().Should().BeOfType<FallbackResilienceStrategy>(); | ||
} | ||
|
||
[Fact] | ||
public void AddFallback_Generic_Ok() | ||
{ | ||
var strategy = _builder | ||
.AddFallback<int>( | ||
handler => handler.HandleResult(-1).HandleException<InvalidOperationException>(), | ||
(_, args) => | ||
{ | ||
args.Context.Should().NotBeNull(); | ||
return new ValueTask<int>(1); | ||
}) | ||
.Build(); | ||
|
||
strategy.Execute(_ => -1).Should().Be(1); | ||
strategy.Execute<int>(_ => throw new InvalidOperationException()).Should().Be(1); | ||
} | ||
|
||
[Fact] | ||
public void AddFallback_InvalidOptions_Throws() | ||
{ | ||
_builder | ||
.Invoking(b => b.AddFallback(new FallbackStrategyOptions { Handler = null! })) | ||
.Should() | ||
.Throw<ValidationException>() | ||
.WithMessage("The fallback strategy options are invalid.*"); | ||
} | ||
|
||
[Fact] | ||
public void AddFallbackT_InvalidOptions_Throws() | ||
{ | ||
_builder | ||
.Invoking(b => b.AddFallback(new FallbackStrategyOptions<double> { ShouldHandle = null! })) | ||
.Should() | ||
.Throw<ValidationException>() | ||
.WithMessage("The fallback strategy options are invalid.*"); | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
src/Polly.Core.Tests/Fallback/FallbackResilienceStrategyTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using Polly.Fallback; | ||
using Polly.Strategy; | ||
|
||
namespace Polly.Core.Tests.Fallback; | ||
|
||
public class FallbackResilienceStrategyTests | ||
{ | ||
private readonly FallbackStrategyOptions _options = new(); | ||
private readonly List<IResilienceArguments> _args = new(); | ||
private readonly ResilienceStrategyTelemetry _telemetry; | ||
|
||
public FallbackResilienceStrategyTests() => _telemetry = TestUtilities.CreateResilienceTelemetry(args => _args.Add(args)); | ||
|
||
[Fact] | ||
public void Ctor_Ok() | ||
{ | ||
Create().Should().NotBeNull(); | ||
} | ||
|
||
[Fact] | ||
public void NoHandler_Skips() | ||
{ | ||
Create().Execute(_ => { }); | ||
|
||
_args.Should().BeEmpty(); | ||
} | ||
|
||
[Fact] | ||
public void Handle_Result_Ok() | ||
{ | ||
var called = false; | ||
_options.OnFallback.Register(() => called = true); | ||
_options.Handler.SetFallback<int>(handler => | ||
{ | ||
handler.ShouldHandle.HandleResult(-1); | ||
handler.FallbackAction = (outcome, args) => | ||
{ | ||
outcome.Result.Should().Be(-1); | ||
args.Context.Should().NotBeNull(); | ||
return new ValueTask<int>(0); | ||
}; | ||
}); | ||
|
||
Create().Execute(_ => -1).Should().Be(0); | ||
|
||
_args.Should().ContainSingle(v => v is HandleFallbackArguments); | ||
called.Should().BeTrue(); | ||
} | ||
|
||
[Fact] | ||
public void Handle_Exception_Ok() | ||
{ | ||
var called = false; | ||
_options.OnFallback.Register(() => called = true); | ||
_options.Handler.SetFallback<int>(handler => | ||
{ | ||
handler.ShouldHandle.HandleException<InvalidOperationException>(); | ||
handler.FallbackAction = (outcome, args) => | ||
{ | ||
outcome.Exception.Should().BeOfType<InvalidOperationException>(); | ||
args.Context.Should().NotBeNull(); | ||
return new ValueTask<int>(0); | ||
}; | ||
}); | ||
|
||
Create().Execute<int>(_ => throw new InvalidOperationException()).Should().Be(0); | ||
|
||
_args.Should().ContainSingle(v => v is HandleFallbackArguments); | ||
called.Should().BeTrue(); | ||
} | ||
|
||
[Fact] | ||
public void Handle_UnhandledException_Ok() | ||
{ | ||
var called = false; | ||
var fallbackActionCalled = false; | ||
|
||
_options.OnFallback.Register(() => called = true); | ||
_options.Handler.SetFallback<int>(handler => | ||
{ | ||
handler.ShouldHandle.HandleException<InvalidOperationException>(); | ||
handler.FallbackAction = (_, _) => | ||
{ | ||
fallbackActionCalled = true; | ||
return new ValueTask<int>(0); | ||
}; | ||
}); | ||
|
||
Create().Invoking(s => s.Execute<int>(_ => throw new ArgumentException())).Should().Throw<ArgumentException>(); | ||
|
||
_args.Should().BeEmpty(); | ||
called.Should().BeFalse(); | ||
fallbackActionCalled.Should().BeFalse(); | ||
} | ||
|
||
[Fact] | ||
public void Handle_UnhandledResult_Ok() | ||
{ | ||
var called = false; | ||
var fallbackActionCalled = false; | ||
|
||
_options.OnFallback.Register(() => called = true); | ||
_options.Handler.SetFallback<int>(handler => | ||
{ | ||
handler.ShouldHandle.HandleResult(-1); | ||
handler.FallbackAction = (_, _) => | ||
{ | ||
fallbackActionCalled = true; | ||
return new ValueTask<int>(0); | ||
}; | ||
}); | ||
|
||
Create().Execute(_ => 0).Should().Be(0); | ||
_args.Should().BeEmpty(); | ||
called.Should().BeFalse(); | ||
fallbackActionCalled.Should().BeFalse(); | ||
} | ||
|
||
private FallbackResilienceStrategy Create() => new(_options, _telemetry); | ||
} |
Oops, something went wrong.