|
14 | 14 | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
15 | 15 | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
16 | 16 | using Microsoft.AspNetCore.Testing;
|
| 17 | +using Microsoft.Extensions.Configuration; |
17 | 18 | using Microsoft.Extensions.DependencyInjection;
|
18 | 19 | using Microsoft.Extensions.Logging;
|
19 | 20 | using Microsoft.Extensions.Options;
|
| 21 | +using Microsoft.Extensions.Primitives; |
20 | 22 | using Microsoft.Net.Http.Headers;
|
21 | 23 | using Moq;
|
22 | 24 | using Xunit;
|
@@ -455,6 +457,150 @@ public void StartingServerInitializesHeartbeat()
|
455 | 457 | }
|
456 | 458 | }
|
457 | 459 |
|
| 460 | + [Fact] |
| 461 | + public async Task ReloadsOnConfigurationChangeByDefault() |
| 462 | + { |
| 463 | + var currentConfig = new ConfigurationBuilder().AddInMemoryCollection(new[] |
| 464 | + { |
| 465 | + new KeyValuePair<string, string>("Endpoints:A:Url", "http://*:5000"), |
| 466 | + new KeyValuePair<string, string>("Endpoints:B:Url", "http://*:5001"), |
| 467 | + }).Build(); |
| 468 | + |
| 469 | + Action changeCallback = null; |
| 470 | + |
| 471 | + var mockChangeToken = new Mock<IChangeToken>(); |
| 472 | + mockChangeToken.Setup(t => t.RegisterChangeCallback(It.IsAny<Action<object>>(), It.IsAny<object>())).Returns<Action<object>, object>((callback, state) => |
| 473 | + { |
| 474 | + changeCallback = () => callback(state); |
| 475 | + return Mock.Of<IDisposable>(); |
| 476 | + }); |
| 477 | + |
| 478 | + var mockConfig = new Mock<IConfiguration>(); |
| 479 | + mockConfig.Setup(c => c.GetSection(It.IsAny<string>())).Returns<string>(name => currentConfig.GetSection(name)); |
| 480 | + mockConfig.Setup(c => c.GetChildren()).Returns(() => currentConfig.GetChildren()); |
| 481 | + mockConfig.Setup(c => c.GetReloadToken()).Returns(() => mockChangeToken.Object); |
| 482 | + |
| 483 | + var mockLoggerFactory = new Mock<ILoggerFactory>(); |
| 484 | + mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny<string>())).Returns(Mock.Of<ILogger>()); |
| 485 | + |
| 486 | + var serviceCollection = new ServiceCollection(); |
| 487 | + serviceCollection.AddSingleton(mockLoggerFactory.Object); |
| 488 | + serviceCollection.AddSingleton(Mock.Of<ILogger<KestrelServer>>()); |
| 489 | + |
| 490 | + var options = new KestrelServerOptions |
| 491 | + { |
| 492 | + ApplicationServices = serviceCollection.BuildServiceProvider(), |
| 493 | + }; |
| 494 | + |
| 495 | + options.Configure(mockConfig.Object); |
| 496 | + |
| 497 | + var mockTransports = new List<Mock<IConnectionListener>>(); |
| 498 | + var mockTransportFactory = new Mock<IConnectionListenerFactory>(); |
| 499 | + mockTransportFactory |
| 500 | + .Setup(transportFactory => transportFactory.BindAsync(It.IsAny<EndPoint>(), It.IsAny<CancellationToken>())) |
| 501 | + .Returns<EndPoint, CancellationToken>((e, token) => |
| 502 | + { |
| 503 | + var mockTransport = new Mock<IConnectionListener>(); |
| 504 | + mockTransport |
| 505 | + .Setup(transport => transport.AcceptAsync(It.IsAny<CancellationToken>())) |
| 506 | + .Returns(new ValueTask<ConnectionContext>(result: null)); |
| 507 | + mockTransport |
| 508 | + .Setup(transport => transport.EndPoint).Returns(e); |
| 509 | + |
| 510 | + mockTransports.Add(mockTransport); |
| 511 | + |
| 512 | + return new ValueTask<IConnectionListener>(mockTransport.Object); |
| 513 | + }); |
| 514 | + |
| 515 | + // Don't use "using". Dispose() could hang if test fails. |
| 516 | + var server = new KestrelServer(Options.Create(options), new List<IConnectionListenerFactory>() { mockTransportFactory.Object }, mockLoggerFactory.Object); |
| 517 | + |
| 518 | + await server.StartAsync(new DummyApplication(), CancellationToken.None).DefaultTimeout(); |
| 519 | + |
| 520 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5000), It.IsAny<CancellationToken>()), Times.Once); |
| 521 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5001), It.IsAny<CancellationToken>()), Times.Once); |
| 522 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5002), It.IsAny<CancellationToken>()), Times.Never); |
| 523 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5003), It.IsAny<CancellationToken>()), Times.Never); |
| 524 | + |
| 525 | + Assert.Equal(2, mockTransports.Count); |
| 526 | + |
| 527 | + foreach (var mockTransport in mockTransports) |
| 528 | + { |
| 529 | + mockTransport.Verify(t => t.UnbindAsync(It.IsAny<CancellationToken>()), Times.Never); |
| 530 | + } |
| 531 | + |
| 532 | + currentConfig = new ConfigurationBuilder().AddInMemoryCollection(new[] |
| 533 | + { |
| 534 | + new KeyValuePair<string, string>("Endpoints:A:Url", "http://*:5000"), |
| 535 | + new KeyValuePair<string, string>("Endpoints:B:Url", "http://*:5002"), |
| 536 | + new KeyValuePair<string, string>("Endpoints:C:Url", "http://*:5003"), |
| 537 | + }).Build(); |
| 538 | + |
| 539 | + changeCallback(); |
| 540 | + |
| 541 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5000), It.IsAny<CancellationToken>()), Times.Once); |
| 542 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5001), It.IsAny<CancellationToken>()), Times.Once); |
| 543 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5002), It.IsAny<CancellationToken>()), Times.Once); |
| 544 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5003), It.IsAny<CancellationToken>()), Times.Once); |
| 545 | + |
| 546 | + Assert.Equal(4, mockTransports.Count); |
| 547 | + |
| 548 | + foreach (var mockTransport in mockTransports) |
| 549 | + { |
| 550 | + if (((IPEndPoint)mockTransport.Object.EndPoint).Port == 5001) |
| 551 | + { |
| 552 | + mockTransport.Verify(t => t.UnbindAsync(It.IsAny<CancellationToken>()), Times.Once); |
| 553 | + } |
| 554 | + else |
| 555 | + { |
| 556 | + mockTransport.Verify(t => t.UnbindAsync(It.IsAny<CancellationToken>()), Times.Never); |
| 557 | + } |
| 558 | + } |
| 559 | + |
| 560 | + currentConfig = new ConfigurationBuilder().AddInMemoryCollection(new[] |
| 561 | + { |
| 562 | + new KeyValuePair<string, string>("Endpoints:A:Url", "http://*:5000"), |
| 563 | + new KeyValuePair<string, string>("Endpoints:B:Url", "http://*:5002"), |
| 564 | + new KeyValuePair<string, string>("Endpoints:C:Url", "https://*:5003"), |
| 565 | + }).Build(); |
| 566 | + |
| 567 | + changeCallback(); |
| 568 | + |
| 569 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5000), It.IsAny<CancellationToken>()), Times.Once); |
| 570 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5001), It.IsAny<CancellationToken>()), Times.Once); |
| 571 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5002), It.IsAny<CancellationToken>()), Times.Once); |
| 572 | + mockTransportFactory.Verify(f => f.BindAsync(new IPEndPoint(IPAddress.IPv6Any, 5003), It.IsAny<CancellationToken>()), Times.Exactly(2)); |
| 573 | + |
| 574 | + Assert.Equal(5, mockTransports.Count); |
| 575 | + |
| 576 | + var firstPort5003TransportChecked = false; |
| 577 | + |
| 578 | + foreach (var mockTransport in mockTransports) |
| 579 | + { |
| 580 | + var port = ((IPEndPoint)mockTransport.Object.EndPoint).Port; |
| 581 | + if (port == 5001) |
| 582 | + { |
| 583 | + mockTransport.Verify(t => t.UnbindAsync(It.IsAny<CancellationToken>()), Times.Once); |
| 584 | + } |
| 585 | + else if (port == 5003 && !firstPort5003TransportChecked) |
| 586 | + { |
| 587 | + mockTransport.Verify(t => t.UnbindAsync(It.IsAny<CancellationToken>()), Times.Once); |
| 588 | + firstPort5003TransportChecked = true; |
| 589 | + } |
| 590 | + else |
| 591 | + { |
| 592 | + mockTransport.Verify(t => t.UnbindAsync(It.IsAny<CancellationToken>()), Times.Never); |
| 593 | + } |
| 594 | + } |
| 595 | + |
| 596 | + await server.StopAsync(CancellationToken.None).DefaultTimeout(); |
| 597 | + |
| 598 | + foreach (var mockTransport in mockTransports) |
| 599 | + { |
| 600 | + mockTransport.Verify(t => t.UnbindAsync(It.IsAny<CancellationToken>()), Times.Once); |
| 601 | + } |
| 602 | + } |
| 603 | + |
458 | 604 | private static KestrelServer CreateServer(KestrelServerOptions options, ILogger testLogger)
|
459 | 605 | {
|
460 | 606 | return new KestrelServer(Options.Create(options), new List<IConnectionListenerFactory>() { new MockTransportFactory() }, new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) }));
|
|
0 commit comments