单元测试AspNet.Security.OpenIdConnect.Server(OIDC / ASOS)

时间:2016-01-04 15:51:11

标签: asp.net jwt aspnet-contrib

我正在使用Visual Studio 2015 Enterprise Update 1和ASP.NET 5 rc1-final构建一个端点,该端点同时发布和使用JWT令牌,详细信息如here所述。

现在,在测试AspNet.Security.OpenIdConnect.Server(OIDC / ASOS)的某些方面时,我正在进行单元测试并遇到摩擦。具体来说,一些ASOS原语(如LogoutEndpointContext)不是抽象的,因此不容易模拟它们。通常情况下,我只会在这些类型上抛出fake,但DNX似乎不支持假货,至少现在还没有。这些类型也是密封的,所以我不能把它们专门化。

这迫使我编写一些脆弱的反射代码来构造这些密封的ASOS类型。这是一个需要LogoutEndpointContext的示例XUnit测试,因此我可以测试我的OpenIdConnectServerProvider事件处理(在这种情况下,非POST注销应该抛出异常);请注意我必须做的反射才能实例化LogoutEndpointContext:

[Fact]
async public Task API_Initialization_Services_AuthenticatedUser_Authentication_LogoutEndpoint_XSRF_Unit()
{
    // Arrange

        Mock<HttpRequest> mockRequest = new Mock<HttpRequest>();
        mockRequest.SetupGet(a => a.Method).Returns(() => "Not Post");
        Mock<HttpContext> mockContext = new Mock<HttpContext>();
        mockContext.SetupGet(a => a.Request).Returns(() => mockRequest.Object);

        OpenIdConnectServerOptions options = new OpenIdConnectServerOptions();

        OpenIdConnectMessage request = new OpenIdConnectMessage();

        // I would prefer not to use reflection
        var ctorInfo = typeof(LogoutEndpointContext).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();

        LogoutEndpointContext context = (LogoutEndpointContext)ctorInfo.Invoke(new object[] {mockContext.Object,options, request});

    // Act

        AuthenticationEvents authenticationEvents = new AuthenticationEvents();

    // Assert

        await Assert.ThrowsAsync<SecurityTokenValidationException>(() => authenticationEvents.LogoutEndpoint(context));
}

关于如何更好地实例化/模拟/伪造/专门化密封的ASOS类型(如LogoutEndpointContext)的任何建议都将非常感激。

1 个答案:

答案 0 :(得分:0)

在测试严重依赖于HTTP上下文的中间件时,单元测试很少是最简单的选择。我们推荐的方法是通过使用TestServer来设置内存管道来选择功能测试:

public class OAuthLogoutEndpointTests {
    [Fact]
    public async Task EnsureThatOnlyPostLogoutRequestsAreValid() {
        // Arrange
        var server = CreateAuthorizationServer();
        var client = server.CreateClient();

        var request = new HttpRequestMessage(HttpMethod.Get, "/connect/logout");

        // Act
        var response = await client.SendAsync(request);

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    private static TestServer CreateAuthorizationServer(Action<OpenIdConnectServerOptions> configuration = null) {
        var builder = new WebHostBuilder();

        builder.UseEnvironment("Testing");

        builder.ConfigureServices(services => {
            services.AddAuthentication();
            services.AddCaching();
            services.AddLogging();
        });

        builder.Configure(app => {
            // Add a new OpenID Connect server instance.
            app.UseOpenIdConnectServer(options => {
                // Use your own provider:
                // options.Provider = new AuthenticationEvents();

                options.Provider = new OpenIdConnectServerProvider {
                    OnValidateLogoutRequest = context => {
                        // Reject non-POST logout requests.
                        if (!string.Equals(context.HttpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) {
                            context.Reject(
                                error: OpenIdConnectConstants.Errors.InvalidRequest,
                                description: "Only POST requests are supported.");
                        }

                        return Task.FromResult(0);
                    }
                };

                // Run the configuration delegate
                // registered by the unit tests.
                configuration?.Invoke(options);
            });
        });

        return new TestServer(builder);
    }
}

(您可以在此处找到更多测试:https://github.com/aspnet-contrib/AspNet.Security.OAuth.Extensions/blob/master/test/AspNet.Security.OAuth.Introspection.Tests/OAuthIntrospectionMiddlewareTests.cs

编辑:此答案的其余部分不再适用,因为ASOS beta5中已启封了上下文类。

在ASOS中,所有上下文类都已被密封,其构造函数被标记为内部,以防止开发人员实例化它们并在生产代码中手动调用提供程序。也就是说,如果您认为我们不应该这样做,请随意在GitHub存储库上打开新票。

最后一句话:如果您希望您的OIDC服务器符合规范,拒绝GET请求可能不是一个好主意,因为它是规范中明确提到的唯一动词。