MVC 应用程序的模拟 HttpRequest 和 HttpResponse

发布于 2024-10-12 10:27:36 字数 8857 浏览 0 评论 0原文

我目前正在编写一些单元测试来检查我们编写的 ASP MVC 应用程序的功能和正确工作方式。 在此 MVC 应用程序中,我使用特殊的 ActionFilterAttribute,它允许在向 MVC 应用程序发出请求时进行身份验证。

此 ActionFilterAttribute 的代码是这样的:

using System;
using System.Security.Authentication;
using System.Text;
using System.Web.Mvc;
using TenForce.Execution.Framework;
using TenForce.Execution.Api2.Implementation;

namespace TenForce.Execution.Web.Filters
{
     /// <summary>
     /// This class defines a custom Authentication attribute that can be applied on      controllers.
     /// This results in authentication occurring on all actions that are beeing defined in the controller
     /// who implements this filter.
     /// </summary>
     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
     public class AuthenticationFilter : ActionFilterAttribute
     {
         #region IAuthorizationFilter Members

         /// <summary>
         /// This function get's called by the Mvc framework prior to performing any actions on
         /// the controller. The function will check if a call is authorized by the caller.
    /// The function will extract the username and password from the HTTP headers send by
    /// the caller and will validate these against the database to see if there is a valid
    /// account for the user.
    /// If the user can be found in the database, operations will resume, otherwise the action
    /// is canceled.
    /// </summary>
    /// <param name="filterContext">The context for the filter.</param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Call the base operations first.
        base.OnActionExecuting(filterContext);

        // Surround the entire authentication process with a try-catch to prevent errors from
        // breaking the code.
        try
        {
            // Extract the custom authorization header from the HTTP headers.
            string customAuthHeader = Encoding.UTF8.GetString(Convert.FromBase64String(filterContext.RequestContext.HttpContext.Request.Headers["TenForce-Auth"]));

            // Split the header in the subcomponents.
            string[] components = customAuthHeader.Split('|');

            // Check if both components are present.
            if (components.Length >= 2)
            {
                // This header consists of 2 parts, the username and password, seperate by a vertical pipe.
                string username = components[0] ?? string.Empty;
                string password = components[1] ?? string.Empty;
                string databaseId = Authenticator.ConstructDatabaseId(filterContext.HttpContext.Request.RawUrl);

                // Validate the user against the database.
                if (Authenticator.Authenticate(username, password, databaseId))
                {
                    // The request is valid, so add the custom header to inform the request was
                    // authorized.
                    AllowRequest(filterContext);
                    return;
                }

                throw new InvalidCredentialException(@"The provided username & password combination is invalid. Username : " + username);
            }

            // If we reach this point, the authorization request is no longer valid.
            throw new InvalidCredentialException(@"Insufficient parameters supplied for a valid authentication.");
        }
        catch (Exception ex)
        {
            // Log the exception that has occurred.
            Logger.Log(GetType(), ex);

            // Cancel the request, as we could not properly process it.
            CancelRequest(filterContext);
        }
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Cancels the Athorization and adds the custom tenforce header to the response to
    /// inform the caller that his call has been denied.
    /// </summary>
    /// <param name="authContext">The authorizationContxt that needs to be canceled.</param>        
    private static void CancelRequest(ActionExecutingContext authContext)
    {
        authContext.Result = new HttpUnauthorizedResult();
        if (!authContext.RequestContext.HttpContext.Request.ServerVariables[@"SERVER_SOFTWARE"].Contains(@"Microsoft-IIS/7."))
            authContext.HttpContext.Response.AddHeader(@"Tenforce-RAuth", @"DENIED");
        else
            authContext.HttpContext.Response.Headers.Add(@"Tenforce-RAuth", @"DENIED");
    }

    /// <summary>
    /// Allows the Authorization and adds the custom tenforce header to the response to
    /// inform the claler that his call has been allowed.
    /// </summary>
    /// <param name="authContext">The authorizationContext that needs to be allowed.</param>
    private static void AllowRequest(ActionExecutingContext authContext)
    {
        authContext.Result = null;
        if (!authContext.RequestContext.HttpContext.Request.ServerVariables[@"SERVER_SOFTWARE"].Contains(@"Microsoft-IIS/7."))
            authContext.HttpContext.Response.AddHeader(@"Tenforce-RAuth", @"OK");
        else
            authContext.HttpContext.Response.Headers.Add(@"Tenforce-RAuth", @"OK");
    }        

    #endregion
    }
}

我当前面临的问题是我似乎无法正确模拟带有响应标头的部分。我编写了一个 UnitTest,它模拟 HttpRequest 和 HttpResponse 对象并使用请求调用属性函数。我可以遵循代码中的成功登录路径分支进行 IIS7 模拟,因为这依赖于属性,但是当我尝试遵循登录中的 IIS6 分支时,我遇到了空指针异常。

我使用以下代码来构造 MoQ 对象:

/// <summary>
    /// This function is called before running each test and configures the various properties
    /// of the test class so that each test will run with the same settings initialy.
    /// The function will configure the Mock Framework object so that they simulate a proper
    /// web request on the ActionFilter of a Controller.
    /// </summary>
    [SetUp]
    protected void TestSetup()
    {
        // Construct the Mock object required for the test.
        HttpRequest = new Mock<HttpRequestBase>();
        HttpResponse = new Mock<HttpResponseBase>();
        HttpContext = new Mock<HttpContextBase>();
        ActionContext = new Mock<ActionExecutingContext>();
        Filter = new Web.Filters.AuthenticationFilter();

        // Configure the properties to modify the headers, request and response
        // objects starting from the HttpContext base object.
        // Also create the custom header collection and set the test URI.
        ActionContext.SetupGet(c => c.HttpContext).Returns(HttpContext.Object);
        HttpContext.SetupGet(r => r.Request).Returns(HttpRequest.Object);
        HttpContext.SetupGet(r => r.Response).Returns(HttpResponse.Object);
        HttpResponse.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection());
        HttpRequest.SetupGet(r => r.RawUrl).Returns(@"http://test.tenforce.tst");
    }

实际测试如下所示:

/// <summary>
    /// <para>This test will call the ActionFilter and perform a standard authorization request against the
    /// database using the credentials of the system administrator account. The test relies on the MoQ
    /// framework to mock several of the key components in the MVC Framework such as the HttpRequest, 
    /// HttpResponse and HttpContext objects.</para>
    /// <para>The test expects the authentication to succeed, and relies on the IIS6 implementation.</para>
    /// </summary>
    [Test, MaxDuration]
    public void SuccessfullAuthenticationOnIis6()
    {
        // Configure the Authentication header of the request, so that a valid authentication
        // can take place. We want valid login credentials when the filter requests the header.
        HttpRequest.SetupGet(r => r.Headers).Returns(new System.Net.WebHeaderCollection { { @"TenForce-Auth", CorrectAuthToken } });
        HttpRequest.SetupGet(r => r.ServerVariables).Returns(
            new System.Collections.Specialized.NameValueCollection { { @"SERVER_SOFTWARE", @"Microsoft-IIS/6.0" } });
        HttpResponse.SetupGet(r => r.Headers).Returns(new System.Collections.Specialized.NameValueCollection());
        HttpResponse.Setup(r => r.AddHeader(@"TenForce-RAuth", @"OK"));

        // Call the action on the filter and check the response.
        Filter.OnActionExecuting(ActionContext.Object);

        // Check the ActionResult to null and that the response header contains the correct value.
        Assert.IsTrue(ActionContext.Object.Result == null);
        Assert.IsTrue(ActionContext.Object.HttpContext.Response.Headers["TenForce-RAuth"].Equals(@"OK"));
    }

最后一个断言失败,因为未设置标头。我已使用调试器单步执行代码,并且过滤器实际上设置了标头,因此我认为 MoQ 对象未正确配置为处理请求。

有人可以解释一下如何让我的 HttpResponse 接受 Headers.Add() 请求吗?

I'm currently writing some unit tests to check the functionality and correct workings of the ASP MVC application that we have written.
In this MVC Application, I'm using a special ActionFilterAttribute that allows for authentication when making requests to the MVC Application.

The code for this ActionFilterAttribute is this:

using System;
using System.Security.Authentication;
using System.Text;
using System.Web.Mvc;
using TenForce.Execution.Framework;
using TenForce.Execution.Api2.Implementation;

namespace TenForce.Execution.Web.Filters
{
     /// <summary>
     /// This class defines a custom Authentication attribute that can be applied on      controllers.
     /// This results in authentication occurring on all actions that are beeing defined in the controller
     /// who implements this filter.
     /// </summary>
     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
     public class AuthenticationFilter : ActionFilterAttribute
     {
         #region IAuthorizationFilter Members

         /// <summary>
         /// This function get's called by the Mvc framework prior to performing any actions on
         /// the controller. The function will check if a call is authorized by the caller.
    /// The function will extract the username and password from the HTTP headers send by
    /// the caller and will validate these against the database to see if there is a valid
    /// account for the user.
    /// If the user can be found in the database, operations will resume, otherwise the action
    /// is canceled.
    /// </summary>
    /// <param name="filterContext">The context for the filter.</param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Call the base operations first.
        base.OnActionExecuting(filterContext);

        // Surround the entire authentication process with a try-catch to prevent errors from
        // breaking the code.
        try
        {
            // Extract the custom authorization header from the HTTP headers.
            string customAuthHeader = Encoding.UTF8.GetString(Convert.FromBase64String(filterContext.RequestContext.HttpContext.Request.Headers["TenForce-Auth"]));

            // Split the header in the subcomponents.
            string[] components = customAuthHeader.Split('|');

            // Check if both components are present.
            if (components.Length >= 2)
            {
                // This header consists of 2 parts, the username and password, seperate by a vertical pipe.
                string username = components[0] ?? string.Empty;
                string password = components[1] ?? string.Empty;
                string databaseId = Authenticator.ConstructDatabaseId(filterContext.HttpContext.Request.RawUrl);

                // Validate the user against the database.
                if (Authenticator.Authenticate(username, password, databaseId))
                {
                    // The request is valid, so add the custom header to inform the request was
                    // authorized.
                    AllowRequest(filterContext);
                    return;
                }

                throw new InvalidCredentialException(@"The provided username & password combination is invalid. Username : " + username);
            }

            // If we reach this point, the authorization request is no longer valid.
            throw new InvalidCredentialException(@"Insufficient parameters supplied for a valid authentication.");
        }
        catch (Exception ex)
        {
            // Log the exception that has occurred.
            Logger.Log(GetType(), ex);

            // Cancel the request, as we could not properly process it.
            CancelRequest(filterContext);
        }
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Cancels the Athorization and adds the custom tenforce header to the response to
    /// inform the caller that his call has been denied.
    /// </summary>
    /// <param name="authContext">The authorizationContxt that needs to be canceled.</param>        
    private static void CancelRequest(ActionExecutingContext authContext)
    {
        authContext.Result = new HttpUnauthorizedResult();
        if (!authContext.RequestContext.HttpContext.Request.ServerVariables[@"SERVER_SOFTWARE"].Contains(@"Microsoft-IIS/7."))
            authContext.HttpContext.Response.AddHeader(@"Tenforce-RAuth", @"DENIED");
        else
            authContext.HttpContext.Response.Headers.Add(@"Tenforce-RAuth", @"DENIED");
    }

    /// <summary>
    /// Allows the Authorization and adds the custom tenforce header to the response to
    /// inform the claler that his call has been allowed.
    /// </summary>
    /// <param name="authContext">The authorizationContext that needs to be allowed.</param>
    private static void AllowRequest(ActionExecutingContext authContext)
    {
        authContext.Result = null;
        if (!authContext.RequestContext.HttpContext.Request.ServerVariables[@"SERVER_SOFTWARE"].Contains(@"Microsoft-IIS/7."))
            authContext.HttpContext.Response.AddHeader(@"Tenforce-RAuth", @"OK");
        else
            authContext.HttpContext.Response.Headers.Add(@"Tenforce-RAuth", @"OK");
    }        

    #endregion
    }
}

The problem I'm currently facing is that I can't seem to properly mock the section with the response headers. I've written a UnitTest that mocks a HttpRequest and HttpResponse object and calls the attribute function with the request. I can follow the successful login path branch in the code for an IIS7 simulation as this relies on properties, but when I try to follow the IIS6 branch in a login, I'm getting null pointer exceptions.

I use the following code to construct the MoQ objects:

/// <summary>
    /// This function is called before running each test and configures the various properties
    /// of the test class so that each test will run with the same settings initialy.
    /// The function will configure the Mock Framework object so that they simulate a proper
    /// web request on the ActionFilter of a Controller.
    /// </summary>
    [SetUp]
    protected void TestSetup()
    {
        // Construct the Mock object required for the test.
        HttpRequest = new Mock<HttpRequestBase>();
        HttpResponse = new Mock<HttpResponseBase>();
        HttpContext = new Mock<HttpContextBase>();
        ActionContext = new Mock<ActionExecutingContext>();
        Filter = new Web.Filters.AuthenticationFilter();

        // Configure the properties to modify the headers, request and response
        // objects starting from the HttpContext base object.
        // Also create the custom header collection and set the test URI.
        ActionContext.SetupGet(c => c.HttpContext).Returns(HttpContext.Object);
        HttpContext.SetupGet(r => r.Request).Returns(HttpRequest.Object);
        HttpContext.SetupGet(r => r.Response).Returns(HttpResponse.Object);
        HttpResponse.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection());
        HttpRequest.SetupGet(r => r.RawUrl).Returns(@"http://test.tenforce.tst");
    }

The actuall test looks like this:

/// <summary>
    /// <para>This test will call the ActionFilter and perform a standard authorization request against the
    /// database using the credentials of the system administrator account. The test relies on the MoQ
    /// framework to mock several of the key components in the MVC Framework such as the HttpRequest, 
    /// HttpResponse and HttpContext objects.</para>
    /// <para>The test expects the authentication to succeed, and relies on the IIS6 implementation.</para>
    /// </summary>
    [Test, MaxDuration]
    public void SuccessfullAuthenticationOnIis6()
    {
        // Configure the Authentication header of the request, so that a valid authentication
        // can take place. We want valid login credentials when the filter requests the header.
        HttpRequest.SetupGet(r => r.Headers).Returns(new System.Net.WebHeaderCollection { { @"TenForce-Auth", CorrectAuthToken } });
        HttpRequest.SetupGet(r => r.ServerVariables).Returns(
            new System.Collections.Specialized.NameValueCollection { { @"SERVER_SOFTWARE", @"Microsoft-IIS/6.0" } });
        HttpResponse.SetupGet(r => r.Headers).Returns(new System.Collections.Specialized.NameValueCollection());
        HttpResponse.Setup(r => r.AddHeader(@"TenForce-RAuth", @"OK"));

        // Call the action on the filter and check the response.
        Filter.OnActionExecuting(ActionContext.Object);

        // Check the ActionResult to null and that the response header contains the correct value.
        Assert.IsTrue(ActionContext.Object.Result == null);
        Assert.IsTrue(ActionContext.Object.HttpContext.Response.Headers["TenForce-RAuth"].Equals(@"OK"));
    }

The last assert is failing because the header is not beeing set. I've used the debugger to step through the code and the filter does actually set the header, so I think that the MoQ object is not properly configured to handle the request.

Could someone please shed some light on how I can get my HttpResponse to accept the Headers.Add() request?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

云仙小弟 2024-10-19 10:27:36

看来我已经找到了我必须将以下行添加到我的设置中,以将最小起订量的调用委托给实现:

HttpResponse.Setup(r => r.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((x,y) => HttpResponse.Object.Headers.Add(x, y));

相当简单,但你必须知道它......

Seems i've found it. I had to add the following line to my setup to delegate the call of the moq to the implementation:

HttpResponse.Setup(r => r.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((x,y) => HttpResponse.Object.Headers.Add(x, y));

Rather simple, but you have to know it...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文