c# - net - Test d'unité IAuthenticationFilter dans WebApi 2




web api c# authentication (2)

Il est possible de réaliser ce que vous vouliez, cependant, comme aucun des objets dans le contexte de la chaîne.Request.Headers.Authorization expose des propriétés virtuelles Mock ou tout autre cadre ne fournira pas beaucoup d'aide pour vous. Voici le code pour obtenir HttpAuthenticationContext avec des valeurs mockées:

HttpRequestMessage request = new HttpRequestMessage();
HttpControllerContext controllerContext = new HttpControllerContext();
controllerContext.Request = request;
HttpActionContext context = new HttpActionContext();
context.ControllerContext = controllerContext;
HttpAuthenticationContext m = new HttpAuthenticationContext(context, null);
HttpRequestHeaders headers = request.Headers;
AuthenticationHeaderValue authorization = new AuthenticationHeaderValue("scheme");
headers.Authorization = authorization;

Vous avez simplement besoin de créer de façon ordinaire certains objets et de les transmettre à d'autres avec des constructeurs ou des propriétés. La raison pour laquelle j'ai créé des instances HttpControllerContext et HttpActionContext est parce que la propriété HttpAuthenticationContext.Request a seulement une partie - sa valeur peut être définie via HttpControllerContext. En utilisant la méthode ci-dessus vous pouvez tester votre filtre, mais vous ne pouvez pas vérifier dans le test si les propriétés des objets ci-dessus ont été touchées simplement parce qu'elles ne sont pas écrasables - sans cela il n'y a aucune possibilité de suivre cela.

J'essaie de tester un filtre d'authentification de base que j'ai écrit pour un projet WebApi 2, mais je rencontre des difficultés pour me moquer de l'objet HttpAuthenticationContext requis dans l'appel OnAuthentication.

public override void OnAuthentication(HttpAuthenticationContext context)
{
    base.OnAuthentication(context);

    var authHeader = context.Request.Headers.Authorization;

    ... the rest of my code here
}

La ligne dans l'implémentation que j'essaye de mettre en place pour moquer est celle qui définit la variable authHeader.

Cependant, je ne peux pas mocker l'objet Headers car il est scellé. Et je ne peux pas me moquer de la requête et définir un en-tête mocké car c'est une propriété non-virtuelle. Et ainsi de suite jusqu'à la chaîne jusqu'au contexte.

Quelqu'un at-il testé avec succès une nouvelle implémentation IAuthenticationFilter?

J'utilise Moq mais je suis sûr que je pourrais suivre dans toute bibliothèque moqueuse si vous avez un exemple de code.

Merci pour toute aide.


J'ai pu utiliser la réponse de @ mr100 pour commencer à résoudre mon problème, qui consistait à tester un certain nombre d'implémentations de IAuthorizationFilter. Pour tester efficacement l'autorisation d'API Web, vous ne pouvez pas vraiment utiliser AuthorizationFilterAttribute et vous devez appliquer un filtre global qui vérifie la présence d'attributs passifs sur les contrôleurs / actions. Longue histoire courte, j'ai développé la réponse de @ mr100 pour inclure des mock pour les descripteurs de contrôleur / action qui vous permettent de tester avec / sans la présence de vos attributs. À titre d'exemple, j'inclurai le plus simple des deux filtres nécessaires au test unitaire qui force les connexions HTTPS pour les contrôleurs / actions spécifiés (ou globalement si vous voulez):

C'est l'attribut qui est appliqué partout où vous voulez forcer une connexion HTTPS, notez qu'il ne fait rien (c'est passif):

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class HttpsRequiredAttribute : Attribute
{       
    public HttpsRequiredAttribute () { }
}

C'est le filtre qui vérifie à chaque requête si l'attribut est présent et si la connexion est sur HTTPS ou non:

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();            
    }
}

Et enfin un test pour prouver qu'il renvoie un statut de 403 interdit lorsque https est requis mais pas utilisé (en utilisant beaucoup de réponse de @ mr100 ici):

[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);
}




moq