我能够使用来自 @mr100 的答案开始解决我的问题,即对几个 IAuthorizationFilter 实现进行单元测试。为了有效地对 web api 授权进行单元测试,您不能真正使用 AuthorizationFilterAttribute 并且您必须应用一个全局过滤器来检查控制器/操作上是否存在被动属性。长话短说,我扩展了来自@mr100 的答案,包括控制器/动作描述符的模拟,让您可以在有/没有属性的情况下进行测试。举例来说,我将包含我需要进行单元测试的两个过滤器中更简单的一个,它强制指定控制器/操作的 HTTPS 连接(或者如果需要,可以全局连接):
这是在您想要强制 HTTPS 连接时应用的属性,请注意它不会做任何事情(它是被动的):
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class HttpsRequiredAttribute : Attribute
{
public HttpsRequiredAttribute () { }
}
这是在每个请求上检查属性是否存在以及连接是否通过 HTTPS 的过滤器:
public class HttpsFilter : IAuthorizationFilter
{
public bool AllowMultiple => false;
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
List<HttpsRequiredAttribute> action = actionContext.ActionDescriptor.GetCustomAttributes<HttpsRequiredAttribute>().ToList();
List<HttpsRequiredAttribute> controller = actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<HttpsRequiredAttribute>().ToList();
// if neither the controller or action have the HttpsRequiredAttribute then don't bother checking if connection is HTTPS
if (!action.Any() && !controller.Any())
return continuation();
// if HTTPS is required but the connection is not HTTPS return a 403 forbidden
if (!string.Equals(actionContext.Request.RequestUri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
return Task.Factory.StartNew(() => new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Https Required",
Content = new StringContent("Https Required")
});
}
return continuation();
}
}
最后一个测试证明它在需要但不使用 https 时返回 403 禁止状态(这里使用了很多 @mr100 的答案):
[TestMethod]
public void HttpsFilter_Forbidden403_WithHttpWhenHttpsIsRequiredByAction()
{
HttpRequestMessage requestMessage = new HttpRequestMessage();
requestMessage.SetRequestContext(new HttpRequestContext());
requestMessage.RequestUri = new Uri("http://www.some-uri.com"); // note the http here (not https)
HttpControllerContext controllerContext = new HttpControllerContext();
controllerContext.Request = requestMessage;
Mock<HttpControllerDescriptor> controllerDescriptor = new Mock<HttpControllerDescriptor>();
controllerDescriptor.Setup(m => m.GetCustomAttributes<HttpsRequiredAttribute>()).Returns(new Collection<HttpsRequiredAttribute>()); // empty collection for controller
Mock<HttpActionDescriptor> actionDescriptor = new Mock<HttpActionDescriptor>();
actionDescriptor.Setup(m => m.GetCustomAttributes<HttpsRequiredAttribute>()).Returns(new Collection<HttpsRequiredAttribute>() { new HttpsRequiredAttribute() }); // collection has one attribute for action
actionDescriptor.Object.ControllerDescriptor = controllerDescriptor.Object;
HttpActionContext actionContext = new HttpActionContext();
actionContext.ControllerContext = controllerContext;
actionContext.ActionDescriptor = actionDescriptor.Object;
HttpAuthenticationContext authContext = new HttpAuthenticationContext(actionContext, null);
Func<Task<HttpResponseMessage>> continuation = () => Task.Factory.StartNew(() => new HttpResponseMessage() { StatusCode = HttpStatusCode.OK });
HttpsFilter filter = new HttpsFilter();
HttpResponseMessage response = filter.ExecuteAuthorizationFilterAsync(actionContext, new CancellationTokenSource().Token, continuation).Result;
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
}