File: HttpsRedirectionMiddlewareTests.cs
Web Access
Project: src\src\Middleware\HttpsPolicy\test\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj (Microsoft.AspNetCore.HttpsPolicy.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net;
using System.Net.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
 
namespace Microsoft.AspNetCore.HttpsPolicy.Tests;
 
public class HttpsRedirectionMiddlewareTests
{
    [Fact]
    public async Task SetOptions_NotEnabledByDefault()
    {
        var sink = new TestSink(
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>,
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>);
        var loggerFactory = new TestLoggerFactory(sink, enabled: true);
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ILoggerFactory>(loggerFactory);
                })
                .Configure(app =>
                {
                    app.UseHttpsRedirection();
                    app.Run(context =>
                    {
                        return context.Response.WriteAsync("Hello world");
                    });
                });
            }).Build();
 
        await host.StartAsync();
 
        var server = host.GetTestServer();
        var client = server.CreateClient();
 
        var request = new HttpRequestMessage(HttpMethod.Get, "");
 
        var response = await client.SendAsync(request);
 
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 
        var logMessages = sink.Writes.ToList();
 
        Assert.Single(logMessages);
        var message = logMessages.Single();
        Assert.Equal(LogLevel.Warning, message.LogLevel);
        Assert.Equal("Failed to determine the https port for redirect.", message.State.ToString());
    }
 
    [Theory]
    [InlineData(302, 5001, "https://localhost:5001/")]
    [InlineData(307, 1, "https://localhost:1/")]
    [InlineData(308, 3449, "https://localhost:3449/")]
    [InlineData(301, 5050, "https://localhost:5050/")]
    [InlineData(301, 443, "https://localhost/")]
    public async Task SetOptions_SetStatusCodeHttpsPort(int statusCode, int? httpsPort, string expected)
    {
        var sink = new TestSink(
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>,
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>);
        var loggerFactory = new TestLoggerFactory(sink, enabled: true);
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ILoggerFactory>(loggerFactory);
                    services.Configure<HttpsRedirectionOptions>(options =>
                    {
                        options.RedirectStatusCode = statusCode;
                        options.HttpsPort = httpsPort;
                    });
                })
                .Configure(app =>
                {
                    app.UseHttpsRedirection();
                    app.Run(context =>
                    {
                        return context.Response.WriteAsync("Hello world");
                    });
                });
            }).Build();
 
        await host.StartAsync();
 
        var server = host.GetTestServer();
        var client = server.CreateClient();
 
        var request = new HttpRequestMessage(HttpMethod.Get, "");
 
        var response = await client.SendAsync(request);
 
        Assert.Equal(statusCode, (int)response.StatusCode);
        Assert.Equal(expected, response.Headers.Location.ToString());
 
        var logMessages = sink.Writes.ToList();
 
        Assert.Single(logMessages);
        var message = logMessages.Single();
        Assert.Equal(LogLevel.Debug, message.LogLevel);
        Assert.Equal($"Redirecting to '{expected}'.", message.State.ToString());
    }
 
    [Theory]
    [InlineData(302, 5001, "https://localhost:5001/")]
    [InlineData(307, 1, "https://localhost:1/")]
    [InlineData(308, 3449, "https://localhost:3449/")]
    [InlineData(301, 5050, "https://localhost:5050/")]
    [InlineData(301, 443, "https://localhost/")]
    public async Task SetOptionsThroughHelperMethod_SetStatusCodeAndHttpsPort(int statusCode, int? httpsPort, string expectedUrl)
    {
        var sink = new TestSink(
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>,
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>);
        var loggerFactory = new TestLoggerFactory(sink, enabled: true);
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ILoggerFactory>(loggerFactory);
                    services.AddHttpsRedirection(options =>
                    {
                        options.RedirectStatusCode = statusCode;
                        options.HttpsPort = httpsPort;
                    });
                })
                .Configure(app =>
                {
                    app.UseHttpsRedirection();
                    app.Run(context =>
                    {
                        return context.Response.WriteAsync("Hello world");
                    });
                });
            }).Build();
 
        await host.StartAsync();
 
        var server = host.GetTestServer();
        var client = server.CreateClient();
 
        var request = new HttpRequestMessage(HttpMethod.Get, "");
 
        var response = await client.SendAsync(request);
 
        Assert.Equal(statusCode, (int)response.StatusCode);
        Assert.Equal(expectedUrl, response.Headers.Location.ToString());
 
        var logMessages = sink.Writes.ToList();
 
        Assert.Single(logMessages);
        var message = logMessages.Single();
        Assert.Equal(LogLevel.Debug, message.LogLevel);
        Assert.Equal($"Redirecting to '{expectedUrl}'.", message.State.ToString());
    }
 
    [Theory]
    [InlineData(null, null, "https://localhost:4444/", "https://localhost:4444/")]
    [InlineData(null, null, "https://localhost:443/", "https://localhost/")]
    [InlineData(null, null, "https://localhost/", "https://localhost/")]
    [InlineData(null, "5000", "https://localhost:4444/", "https://localhost:5000/")]
    [InlineData(null, "443", "https://localhost:4444/", "https://localhost/")]
    [InlineData(443, "5000", "https://localhost:4444/", "https://localhost/")]
    [InlineData(4000, "5000", "https://localhost:4444/", "https://localhost:4000/")]
    [InlineData(5000, null, "https://localhost:4444/", "https://localhost:5000/")]
    public async Task SetHttpsPortEnvironmentVariableAndServerFeature_ReturnsCorrectStatusCodeOnResponse(
        int? optionsHttpsPort, string configHttpsPort, string serverAddressFeatureUrl, string expectedUrl)
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddHttpsRedirection(options =>
                    {
                        options.HttpsPort = optionsHttpsPort;
                    });
                })
                .Configure(app =>
                {
                    app.UseHttpsRedirection();
                    app.Run(context =>
                    {
                        return context.Response.WriteAsync("Hello world");
                    });
                });
 
                webHostBuilder.UseSetting("HTTPS_PORT", configHttpsPort);
            }).Build();
 
        var server = host.GetTestServer();
        server.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
        if (serverAddressFeatureUrl != null)
        {
            server.Features.Get<IServerAddressesFeature>().Addresses.Add(serverAddressFeatureUrl);
        }
 
        await host.StartAsync();
 
        var client = server.CreateClient();
 
        var request = new HttpRequestMessage(HttpMethod.Get, "");
 
        var response = await client.SendAsync(request);
 
        Assert.Equal(expectedUrl, response.Headers.Location.ToString());
    }
 
    [Fact]
    public async Task SetServerAddressesFeature_SingleHttpsAddress_Success()
    {
        var sink = new TestSink(
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>,
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>);
        var loggerFactory = new TestLoggerFactory(sink, enabled: true);
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ILoggerFactory>(loggerFactory);
                })
               .Configure(app =>
               {
                   app.UseHttpsRedirection();
                   app.Run(context =>
                   {
                       return context.Response.WriteAsync("Hello world");
                   });
               });
            }).Build();
 
        var server = host.GetTestServer();
        server.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
 
        server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5050");
        await host.StartAsync();
        var client = server.CreateClient();
 
        var request = new HttpRequestMessage(HttpMethod.Get, "");
 
        var response = await client.SendAsync(request);
 
        Assert.Equal("https://localhost:5050/", response.Headers.Location.ToString());
 
        var logMessages = sink.Writes.ToList();
 
        Assert.Equal(2, logMessages.Count);
        var message = logMessages.First();
        Assert.Equal(LogLevel.Debug, message.LogLevel);
        Assert.Equal("Https port '5050' discovered from server endpoints.", message.State.ToString());
 
        message = logMessages.Skip(1).First();
        Assert.Equal(LogLevel.Debug, message.LogLevel);
        Assert.Equal("Redirecting to 'https://localhost:5050/'.", message.State.ToString());
    }
 
    [Fact]
    public async Task SetServerAddressesFeature_MultipleHttpsAddresses_Throws()
    {
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
               .Configure(app =>
               {
                   app.UseHttpsRedirection();
                   app.Run(context =>
                   {
                       return context.Response.WriteAsync("Hello world");
                   });
               });
            }).Build();
 
        var server = host.GetTestServer();
        server.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
 
        server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5050");
        server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5051");
 
        await host.StartAsync();
 
        var client = server.CreateClient();
 
        var request = new HttpRequestMessage(HttpMethod.Get, "");
 
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => client.SendAsync(request));
        Assert.Equal("Cannot determine the https port from IServerAddressesFeature, multiple values were found. " +
            "Set the desired port explicitly on HttpsRedirectionOptions.HttpsPort.", ex.Message);
    }
 
    [Fact]
    public async Task SetServerAddressesFeature_MultipleHttpsAddressesWithSamePort_Success()
    {
        var sink = new TestSink(
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>,
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>);
        var loggerFactory = new TestLoggerFactory(sink, enabled: true);
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ILoggerFactory>(loggerFactory);
                })
               .Configure(app =>
               {
                   app.UseHttpsRedirection();
                   app.Run(context =>
                   {
                       return context.Response.WriteAsync("Hello world");
                   });
               });
            }).Build();
 
        var server = host.GetTestServer();
        server.Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
        server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://localhost:5050");
        server.Features.Get<IServerAddressesFeature>().Addresses.Add("https://example.com:5050");
 
        await host.StartAsync();
 
        var client = server.CreateClient();
 
        var request = new HttpRequestMessage(HttpMethod.Get, "");
 
        var response = await client.SendAsync(request);
 
        Assert.Equal("https://localhost:5050/", response.Headers.Location.ToString());
 
        var logMessages = sink.Writes.ToList();
 
        Assert.Equal(2, logMessages.Count);
        var message = logMessages.First();
        Assert.Equal(LogLevel.Debug, message.LogLevel);
        Assert.Equal("Https port '5050' discovered from server endpoints.", message.State.ToString());
 
        message = logMessages.Skip(1).First();
        Assert.Equal(LogLevel.Debug, message.LogLevel);
        Assert.Equal("Redirecting to 'https://localhost:5050/'.", message.State.ToString());
    }
 
    [Fact]
    public async Task NoServerAddressFeature_DoesNotThrow_DoesNotRedirect()
    {
        var sink = new TestSink(
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>,
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>);
        var loggerFactory = new TestLoggerFactory(sink, enabled: true);
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ILoggerFactory>(loggerFactory);
                })
                .Configure(app =>
                {
                    app.UseHttpsRedirection();
                    app.Run(context =>
                    {
                        return context.Response.WriteAsync("Hello world");
                    });
                });
            }).Build();
 
        await host.StartAsync();
 
        var server = host.GetTestServer();
        var client = server.CreateClient();
        var request = new HttpRequestMessage(HttpMethod.Get, "");
        var response = await client.SendAsync(request);
        Assert.Equal(200, (int)response.StatusCode);
 
        var logMessages = sink.Writes.ToList();
 
        Assert.Single(logMessages);
        var message = logMessages.First();
        Assert.Equal(LogLevel.Warning, message.LogLevel);
        Assert.Equal("Failed to determine the https port for redirect.", message.State.ToString());
    }
 
    [Fact]
    public async Task SetNullAddressFeature_DoesNotThrow()
    {
        var sink = new TestSink(
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>,
            TestSink.EnableWithTypeName<HttpsRedirectionMiddleware>);
        var loggerFactory = new TestLoggerFactory(sink, enabled: true);
        using var host = new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ILoggerFactory>(loggerFactory);
                })
                .Configure(app =>
                {
                    app.UseHttpsRedirection();
                    app.Run(context =>
                    {
                        return context.Response.WriteAsync("Hello world");
                    });
                });
            }).Build();
 
        var server = host.GetTestServer();
        server.Features.Set<IServerAddressesFeature>(null);
 
        await host.StartAsync();
 
        var client = server.CreateClient();
        var request = new HttpRequestMessage(HttpMethod.Get, "");
        var response = await client.SendAsync(request);
        Assert.Equal(200, (int)response.StatusCode);
 
        var logMessages = sink.Writes.ToList();
 
        Assert.Single(logMessages);
        var message = logMessages.First();
        Assert.Equal(LogLevel.Warning, message.LogLevel);
        Assert.Equal("Failed to determine the https port for redirect.", message.State.ToString());
    }
}