-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
MessageInvokerPoolTests.cs
339 lines (291 loc) · 13.1 KB
/
MessageInvokerPoolTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Request.Middleware;
using Ocelot.Requester;
using Ocelot.Responses;
using System.Diagnostics;
namespace Ocelot.UnitTests.Requester;
[Trait("PR", "1824")]
public class MessageInvokerPoolTests : UnitTest
{
private DownstreamRoute _downstreamRoute1;
private DownstreamRoute _downstreamRoute2;
private MessageInvokerPool _pool;
private HttpMessageInvoker _firstInvoker;
private HttpMessageInvoker _secondInvoker;
private Mock<IDelegatingHandlerHandlerFactory> _handlerFactory;
private readonly Mock<IOcelotLoggerFactory> _ocelotLoggerFactory;
private readonly Mock<IOcelotLogger> _ocelotLogger;
private HttpContext _context;
private HttpResponseMessage _response;
private IWebHost _host;
public MessageInvokerPoolTests()
{
_ocelotLoggerFactory = new Mock<IOcelotLoggerFactory>();
_ocelotLogger = new Mock<IOcelotLogger>();
_ocelotLoggerFactory.Setup(x => x.CreateLogger<MessageInvokerPool>()).Returns(_ocelotLogger.Object);
}
[Fact]
public void If_calling_the_same_downstream_route_twice_should_return_the_same_message_invoker()
{
this.Given(x => x.GivenADownstreamRoute("/super-test"))
.And(x => x.AndAHandlerFactory())
.And(x => x.GivenAMessageInvokerPool())
.When(x => x.WhenGettingMessageInvokerTwice())
.Then(x => x.ThenTheInvokersShouldBeTheSame())
.BDDfy();
}
[Fact]
public void If_calling_two_different_downstream_routes_should_return_different_message_invokers()
{
this.Given(x => x.GivenTwoDifferentDownstreamRoutes("/super-test", "/super-test"))
.And(x => x.AndAHandlerFactory())
.And(x => x.GivenAMessageInvokerPool())
.When(x => x.WhenGettingMessageInvokerForBothRoutes())
.Then(x => x.ThenTheInvokersShouldNotBeTheSame())
.BDDfy();
}
[Fact]
public void If_two_delegating_handlers_are_defined_then_these_should_be_call_in_order()
{
var fakeOne = new FakeDelegatingHandler();
var fakeTwo = new FakeDelegatingHandler();
var handlers = new List<Func<DelegatingHandler>>
{
() => fakeOne,
() => fakeTwo,
};
this.Given(x => GivenTheFactoryReturns(handlers))
.And(x => GivenADownstreamRoute("/super-test"))
.And(x => GivenAMessageInvokerPool())
.And(x => GivenARequest())
.When(x => WhenICallTheClient("http://www.bbc.co.uk"))
.Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo))
.And(x => ThenSomethingIsReturned())
.BDDfy();
}
[Fact]
public void Should_log_if_ignoring_ssl_errors()
{
var qosOptions = new QoSOptionsBuilder()
.Build();
var route = new DownstreamRouteBuilder()
.WithQosOptions(qosOptions)
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue, TimeSpan.FromSeconds(90)))
.WithLoadBalancerKey(string.Empty)
.WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue(string.Empty).Build())
.WithQosOptions(new QoSOptionsBuilder().Build())
.WithDangerousAcceptAnyServerCertificateValidator(true)
.Build();
this.Given(x => GivenTheFactoryReturns(new List<Func<DelegatingHandler>>()))
.And(x => GivenAMessageInvokerPool())
.And(x => GivenARequest(route))
.When(x => WhenICallTheClient("http://www.bbc.co.uk"))
.Then(x => ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged())
.BDDfy();
}
[Fact]
public void Should_re_use_cookies_from_container()
{
var qosOptions = new QoSOptionsBuilder()
.Build();
var route = new DownstreamRouteBuilder()
.WithQosOptions(qosOptions)
.WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false, true, int.MaxValue, TimeSpan.FromSeconds(90)))
.WithLoadBalancerKey(string.Empty)
.WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue(string.Empty).Build())
.WithQosOptions(new QoSOptionsBuilder().Build())
.Build();
this.Given(_ => GivenADownstreamService())
.And(x => GivenTheFactoryReturns(new List<Func<DelegatingHandler>>()))
.And(x => GivenAMessageInvokerPool())
.And(x => GivenARequest(route))
.And(_ => WhenICallTheClient("http://localhost:5003"))
.And(_ => ThenTheCookieIsSet())
.When(_ => WhenICallTheClient("http://localhost:5003"))
.Then(_ => ThenTheResponseIsOk())
.BDDfy();
}
[Theory]
[Trait("Issue", "1833")]
[InlineData(5, 5)]
[InlineData(10, 10)]
public void Create_TimeoutValueInQosOptions_MessageInvokerTimeout(int qosTimeout, int expectedSeconds)
{
// Arrange
var qosOptions = new QoSOptionsBuilder()
.WithTimeoutValue(qosTimeout * 1000)
.Build();
var handlerOptions = new HttpHandlerOptionsBuilder()
.WithUseMaxConnectionPerServer(int.MaxValue)
.Build();
var route = new DownstreamRouteBuilder()
.WithQosOptions(qosOptions)
.WithHttpHandlerOptions(handlerOptions)
.Build();
GivenTheFactoryReturnsNothing();
this.Given(x => GivenTheFactoryReturns(new List<Func<DelegatingHandler>>()))
.And(x => GivenAMessageInvokerPool())
.And(x => GivenARequest(route))
.Then(x => WhenICallTheClientWillThrowAfterTimeout(TimeSpan.FromSeconds(expectedSeconds)))
.BDDfy();
}
private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged()
{
_ocelotLogger.Verify(x => x.LogWarning(
It.Is<Func<string>>(y => y.Invoke() == $"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamRoute, UpstreamPathTemplate: {_context.Items.DownstreamRoute().UpstreamPathTemplate}, DownstreamPathTemplate: {_context.Items.DownstreamRoute().DownstreamPathTemplate}")),
Times.Once);
}
private void ThenTheCookieIsSet()
{
_response.Headers.TryGetValues("Set-Cookie", out var test).ShouldBeTrue();
}
private void GivenADownstreamService()
{
var count = 0;
_host = new WebHostBuilder()
.UseUrls("http://localhost:5003")
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.Configure(app =>
{
app.Run(context =>
{
if (count == 0)
{
context.Response.Cookies.Append("test", "0");
context.Response.StatusCode = 200;
count++;
return Task.CompletedTask;
}
if (count == 1)
{
if (context.Request.Cookies.TryGetValue("test", out var cookieValue) ||
context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue))
{
context.Response.StatusCode = 200;
return Task.CompletedTask;
}
context.Response.StatusCode = 500;
}
return Task.CompletedTask;
});
})
.Build();
_host.Start();
}
private void ThenTheResponseIsOk()
{
_response.StatusCode.ShouldBe(HttpStatusCode.OK);
}
private void GivenARequest(DownstreamRoute downstream)
{
GivenARequest(downstream, "http://localhost:5003");
}
private void GivenARequest(DownstreamRoute downstream, string downstreamUrl)
{
GivenARequestWithAUrlAndMethod(downstream, downstreamUrl, HttpMethod.Get);
}
private void GivenADownstreamRoute(string path) => _downstreamRoute1 = DownstreamRouteFactory(path);
private void GivenTwoDifferentDownstreamRoutes(string path1, string path2)
{
_downstreamRoute1 = DownstreamRouteFactory(path1);
_downstreamRoute2 = DownstreamRouteFactory(path2);
}
private void AndAHandlerFactory() => _handlerFactory = GetHandlerFactory();
private void GivenAMessageInvokerPool() =>
_pool = new MessageInvokerPool(_handlerFactory.Object, _ocelotLoggerFactory.Object);
private void WhenGettingMessageInvokerTwice()
{
_firstInvoker = _pool.Get(_downstreamRoute1);
_secondInvoker = _pool.Get(_downstreamRoute1);
}
private void WhenGettingMessageInvokerForBothRoutes()
{
_firstInvoker = _pool.Get(_downstreamRoute1);
_secondInvoker = _pool.Get(_downstreamRoute2);
}
private void ThenTheInvokersShouldBeTheSame() => Assert.Equal(_firstInvoker, _secondInvoker);
private void ThenTheInvokersShouldNotBeTheSame() => Assert.NotEqual(_firstInvoker, _secondInvoker);
private void GivenARequest(string url) => GivenARequestWithAUrlAndMethod(_downstreamRoute1, url, HttpMethod.Get);
private void GivenARequest() =>
GivenARequestWithAUrlAndMethod(_downstreamRoute1, "http://localhost:5003", HttpMethod.Get);
private void GivenARequestWithAUrlAndMethod(DownstreamRoute downstream, string url, HttpMethod method)
{
_context = new DefaultHttpContext();
_context.Items.UpsertDownstreamRoute(downstream);
_context.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage
{ RequestUri = new Uri(url), Method = method }));
}
private void ThenSomethingIsReturned() => _response.ShouldNotBeNull();
private async Task WhenICallTheClient(string url)
{
var messageInvoker = _pool.Get(_context.Items.DownstreamRoute());
_response = await messageInvoker
.SendAsync(new HttpRequestMessage(HttpMethod.Get, url), CancellationToken.None);
}
private async Task WhenICallTheClientWillThrowAfterTimeout(TimeSpan timeout)
{
var messageInvoker = _pool.Get(_context.Items.DownstreamRoute());
var stopwatch = new Stopwatch();
try
{
stopwatch.Start();
_response = await messageInvoker
.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com"), CancellationToken.None);
}
catch (Exception e)
{
stopwatch.Stop();
var elapsed = stopwatch.Elapsed;
// Compare the elapsed time with the given timeout
// You can use elapsed.CompareTo(timeout) or simply check if elapsed > timeout, based on your requirement
Assert.IsType<TimeoutException>(e);
Assert.True(elapsed >= timeout.Subtract(TimeSpan.FromMilliseconds(500)), $"Elapsed time {elapsed} is smaller than expected timeout {timeout} - 500 ms");
Assert.True(elapsed < timeout.Add(TimeSpan.FromMilliseconds(500)), $"Elapsed time {elapsed} is bigger than expected timeout {timeout} + 500 ms");
}
}
private static void ThenTheFakeAreHandledInOrder(FakeDelegatingHandler fakeOne, FakeDelegatingHandler fakeTwo) =>
fakeOne.TimeCalled.ShouldBeGreaterThan(fakeTwo.TimeCalled);
private void GivenTheFactoryReturnsNothing()
{
var handlers = new List<Func<DelegatingHandler>>();
_handlerFactory = new Mock<IDelegatingHandlerHandlerFactory>();
_handlerFactory
.Setup(x => x.Get(It.IsAny<DownstreamRoute>()))
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
}
private void GivenTheFactoryReturns(List<Func<DelegatingHandler>> handlers)
{
_handlerFactory = new Mock<IDelegatingHandlerHandlerFactory>();
_handlerFactory
.Setup(x => x.Get(It.IsAny<DownstreamRoute>()))
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(handlers));
}
private Mock<IDelegatingHandlerHandlerFactory> GetHandlerFactory()
{
var handlerFactory = new Mock<IDelegatingHandlerHandlerFactory>();
handlerFactory.Setup(x => x.Get(It.IsAny<DownstreamRoute>()))
.Returns(new OkResponse<List<Func<DelegatingHandler>>>(new()));
return handlerFactory;
}
private DownstreamRoute DownstreamRouteFactory(string path)
{
var downstreamRoute = new DownstreamRouteBuilder()
.WithDownstreamPathTemplate(path)
.WithQosOptions(new QoSOptions(new FileQoSOptions()))
.WithLoadBalancerKey(string.Empty)
.WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue(string.Empty).Build())
.WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, false, 10, TimeSpan.FromSeconds(120)))
.WithUpstreamHttpMethod(new() { "Get" })
.Build();
return downstreamRoute;
}
}