Skip to content

Commit 1aeef57

Browse files
authored
Introduce ResilienceStrategyBuilder (App-vNext#1058)
Introduce IResilienceStrategyBuilder
1 parent 5b92f5a commit 1aeef57

13 files changed

+733
-5
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using FluentAssertions;
2+
using Polly.Builder;
3+
using Xunit;
4+
5+
namespace Polly.Core.Tests.Builder;
6+
7+
public class ResilienceStrategyBuilderOptionsTests
8+
{
9+
[Fact]
10+
public void Ctor_EnsureDefaults()
11+
{
12+
var options = new ResilienceStrategyBuilderOptions();
13+
14+
options.BuilderName.Should().Be("");
15+
}
16+
}
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
using System;
2+
using System.ComponentModel.DataAnnotations;
3+
using FluentAssertions;
4+
using Polly.Builder;
5+
using Polly.Core.Tests.Utils;
6+
using Xunit;
7+
8+
namespace Polly.Core.Tests.Builder;
9+
10+
public class ResilienceStrategyBuilderTests
11+
{
12+
[Fact]
13+
public void AddStrategy_Single_Ok()
14+
{
15+
// arrange
16+
var executions = new List<int>();
17+
var builder = new ResilienceStrategyBuilder();
18+
var first = new TestResilienceStrategy
19+
{
20+
Before = (_, _) => executions.Add(1),
21+
After = (_, _) => executions.Add(3),
22+
};
23+
24+
builder.AddStrategy(first);
25+
26+
// act
27+
var strategy = builder.Build();
28+
29+
// assert
30+
strategy.Execute(_ => executions.Add(2));
31+
strategy.Should().BeOfType<TestResilienceStrategy>();
32+
executions.Should().BeInAscendingOrder();
33+
executions.Should().HaveCount(3);
34+
}
35+
36+
[Fact]
37+
public void AddStrategy_Multiple_Ok()
38+
{
39+
// arrange
40+
var executions = new List<int>();
41+
var builder = new ResilienceStrategyBuilder();
42+
var first = new TestResilienceStrategy
43+
{
44+
Before = (_, _) => executions.Add(1),
45+
After = (_, _) => executions.Add(7),
46+
};
47+
var second = new TestResilienceStrategy
48+
{
49+
Before = (_, _) => executions.Add(2),
50+
After = (_, _) => executions.Add(6),
51+
};
52+
var third = new TestResilienceStrategy
53+
{
54+
Before = (_, _) => executions.Add(3),
55+
After = (_, _) => executions.Add(5),
56+
};
57+
58+
builder.AddStrategy(first);
59+
builder.AddStrategy(second);
60+
builder.AddStrategy(third);
61+
62+
// act
63+
var strategy = builder.Build();
64+
strategy
65+
.Should()
66+
.BeOfType<ResilienceStrategyPipeline>()
67+
.Subject
68+
.Strategies.Should().HaveCount(3);
69+
70+
// assert
71+
strategy.Execute(_ => executions.Add(4));
72+
73+
executions.Should().BeInAscendingOrder();
74+
executions.Should().HaveCount(7);
75+
}
76+
77+
[Fact]
78+
public void AddStrategy_Duplicate_Throws()
79+
{
80+
// arrange
81+
var executions = new List<int>();
82+
var builder = new ResilienceStrategyBuilder()
83+
.AddStrategy(NullResilienceStrategy.Instance)
84+
.AddStrategy(NullResilienceStrategy.Instance);
85+
86+
builder.Invoking(b => b.Build())
87+
.Should()
88+
.Throw<InvalidOperationException>()
89+
.WithMessage("The resilience pipeline must contain unique resilience strategies.");
90+
}
91+
92+
[Fact]
93+
public void AddStrategy_MultipleNonDelegating_Ok()
94+
{
95+
// arrange
96+
var executions = new List<int>();
97+
var builder = new ResilienceStrategyBuilder();
98+
var first = new Strategy
99+
{
100+
Before = () => executions.Add(1),
101+
After = () => executions.Add(7),
102+
};
103+
var second = new Strategy
104+
{
105+
Before = () => executions.Add(2),
106+
After = () => executions.Add(6),
107+
};
108+
var third = new Strategy
109+
{
110+
Before = () => executions.Add(3),
111+
After = () => executions.Add(5),
112+
};
113+
114+
builder.AddStrategy(first);
115+
builder.AddStrategy(second);
116+
builder.AddStrategy(third);
117+
118+
// act
119+
var strategy = builder.Build();
120+
121+
// assert
122+
strategy.Execute(_ => executions.Add(4));
123+
124+
executions.Should().BeInAscendingOrder();
125+
executions.Should().HaveCount(7);
126+
}
127+
128+
[Fact]
129+
public void Build_Empty_ReturnsNullResilienceStrategy()
130+
{
131+
new ResilienceStrategyBuilder().Build().Should().BeSameAs(NullResilienceStrategy.Instance);
132+
}
133+
134+
[Fact]
135+
public void AddStrategy_AfterUsed_Throws()
136+
{
137+
var builder = new ResilienceStrategyBuilder();
138+
139+
builder.Build();
140+
141+
builder
142+
.Invoking(b => b.AddStrategy(NullResilienceStrategy.Instance))
143+
.Should()
144+
.Throw<InvalidOperationException>()
145+
.WithMessage("Cannot add any more resilience strategies to the builder after it has been used to build a strategy once.");
146+
}
147+
148+
[Fact]
149+
public void Options_SetNull_Throws()
150+
{
151+
var builder = new ResilienceStrategyBuilder();
152+
153+
builder.Invoking(b => b.Options = null!).Should().Throw<ArgumentNullException>();
154+
}
155+
156+
[Fact]
157+
public void Build_InvalidBuilderOptions_Throw()
158+
{
159+
var builder = new ResilienceStrategyBuilder();
160+
builder.Options.BuilderName = null!;
161+
162+
builder.Invoking(b => b.Build())
163+
.Should()
164+
.Throw<ValidationException>()
165+
.WithMessage(
166+
"""
167+
The 'ResilienceStrategyBuilderOptions' options are not valid.
168+
169+
Validation Errors:
170+
The BuilderName field is required.
171+
""");
172+
}
173+
174+
[Fact]
175+
public void AddStrategy_InvalidOptions_Throws()
176+
{
177+
var builder = new ResilienceStrategyBuilder();
178+
179+
builder
180+
.Invoking(b => b.AddStrategy(NullResilienceStrategy.Instance, new ResilienceStrategyOptions { StrategyName = null!, StrategyType = null! }))
181+
.Should()
182+
.Throw<ValidationException>()
183+
.WithMessage(
184+
"""
185+
The 'ResilienceStrategyOptions' options are not valid.
186+
187+
Validation Errors:
188+
The StrategyName field is required.
189+
The StrategyType field is required.
190+
""");
191+
}
192+
193+
[Fact]
194+
public void AddStrategy_NullFactory_Throws()
195+
{
196+
var builder = new ResilienceStrategyBuilder();
197+
198+
builder
199+
.Invoking(b => b.AddStrategy((Func<ResilienceStrategyBuilderContext, IResilienceStrategy>)null!))
200+
.Should()
201+
.Throw<ArgumentNullException>()
202+
.And.ParamName
203+
.Should()
204+
.Be("factory");
205+
}
206+
207+
[Fact]
208+
public void AddStrategy_CombinePipelines_Ok()
209+
{
210+
// arrange
211+
var executions = new List<int>();
212+
var first = new TestResilienceStrategy
213+
{
214+
Before = (_, _) => executions.Add(1),
215+
After = (_, _) => executions.Add(7),
216+
};
217+
var second = new TestResilienceStrategy
218+
{
219+
Before = (_, _) => executions.Add(2),
220+
After = (_, _) => executions.Add(6),
221+
};
222+
223+
var pipeline1 = new ResilienceStrategyBuilder().AddStrategy(first).AddStrategy(second).Build();
224+
225+
var third = new TestResilienceStrategy
226+
{
227+
Before = (_, _) => executions.Add(3),
228+
After = (_, _) => executions.Add(5),
229+
};
230+
var pipeline2 = new ResilienceStrategyBuilder().AddStrategy(third).Build();
231+
232+
// act
233+
var strategy = new ResilienceStrategyBuilder().AddStrategy(pipeline1).AddStrategy(pipeline2).Build();
234+
235+
// assert
236+
strategy.Execute(_ => executions.Add(4));
237+
238+
executions.Should().BeInAscendingOrder();
239+
executions.Should().HaveCount(7);
240+
}
241+
242+
[Fact]
243+
public void BuildStrategy_EnsureCorrectContext()
244+
{
245+
// arrange
246+
bool verified1 = false;
247+
bool verified2 = false;
248+
249+
var builder = new ResilienceStrategyBuilder
250+
{
251+
Options = new ResilienceStrategyBuilderOptions
252+
{
253+
BuilderName = "builder-name"
254+
}
255+
};
256+
257+
builder.AddStrategy(
258+
context =>
259+
{
260+
context.BuilderName.Should().Be("builder-name");
261+
context.StrategyName.Should().Be("strategy-name");
262+
context.StrategyType.Should().Be("strategy-type");
263+
verified1 = true;
264+
265+
return new TestResilienceStrategy();
266+
},
267+
new ResilienceStrategyOptions { StrategyName = "strategy-name", StrategyType = "strategy-type" });
268+
269+
builder.AddStrategy(
270+
context =>
271+
{
272+
context.BuilderName.Should().Be("builder-name");
273+
context.StrategyName.Should().Be("strategy-name-2");
274+
context.StrategyType.Should().Be("strategy-type-2");
275+
verified2 = true;
276+
277+
return new TestResilienceStrategy();
278+
},
279+
new ResilienceStrategyOptions { StrategyName = "strategy-name-2", StrategyType = "strategy-type-2" });
280+
281+
// act
282+
builder.Build();
283+
284+
// assert
285+
verified1.Should().BeTrue();
286+
verified2.Should().BeTrue();
287+
}
288+
289+
private class Strategy : IResilienceStrategy
290+
{
291+
public Action? Before { get; set; }
292+
293+
public Action? After { get; set; }
294+
295+
async ValueTask<TResult> IResilienceStrategy.ExecuteInternalAsync<TResult, TState>(Func<ResilienceContext, TState, ValueTask<TResult>> callback, ResilienceContext context, TState state)
296+
{
297+
try
298+
{
299+
Before?.Invoke();
300+
return await callback(context, state);
301+
}
302+
finally
303+
{
304+
After?.Invoke();
305+
}
306+
}
307+
}
308+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using FluentAssertions;
2+
using Polly.Builder;
3+
using Xunit;
4+
5+
namespace Polly.Core.Tests.Builder;
6+
7+
public class ResilienceStrategyOptionsTests
8+
{
9+
[Fact]
10+
public void Ctor_EnsureDefaults()
11+
{
12+
var options = new ResilienceStrategyOptions();
13+
14+
options.StrategyType.Should().Be("");
15+
options.StrategyName.Should().Be("");
16+
}
17+
}

0 commit comments

Comments
 (0)