Как смоделировать авторизацию OwinManager для тестирования моего контроллера API

Я использую авторизацию на основе ресурсов ThinkTecture в своем WebApi.

Я пытаюсь проверить один из моих контроллеров, который мне нужен, чтобы проверить доступ внутри функции. Но теперь я больше не могу тестировать функцию, так как я не могу смоделировать метод расширения, и, поскольку это метод nuget, я не могу изменить класс для добавления другого значения.

Мой контроллер выглядит так:

public class AlbumController : ApiController
{
    public async Task<IHttpActionResult> Get(int id)
    {
        if (!(await Request.CheckAccessAsync(ChinookResources.AlbumActions.View, 
                                            ChinookResources.Album,
                                            id.ToString())))
        {
            return this.AccessDenied();
        }

        return Ok();
    }
}    

И ResourceAuthorizationManager устанавливается в автозагрузку следующим образом:

app.UseResourceAuthorization(new ChinookAuthorization());    

Исходный код проекта ThinkTecture находится здесь.

Спасибо за помощь

3 ответа

Решение

ResourceAuthorizationAttribute использует Reqest.CheckAccess, поэтому я не думаю, что это хорошее решение, чтобы абстрагировать реализацию и затем внедрить ее в контроллер, поскольку теоретически ResourceAuthorizationAttribute и созданная служба могут использовать разные реализации метода CheckAccess.

Я выбрал более простой подход, создав BaseController

public class BaseController : ApiController
{
        public virtual Task<bool> CheckAccessAsync(string action, params string[] resources)
        {
            return Request.CheckAccessAsync(action, resources);
        }
}

и сделать CheckAccessAsync виртуальным, чтобы я мог издеваться над ним (например, Moq).

тогда из моего контроллера

public class AlbumController : BaseController
{
    public async Task<IHttpActionResult> Get(int id)
    {
        if (!(await CheckAccessAsync(ChinookResources.AlbumActions.View, 
                                            ChinookResources.Album,
                                            id.ToString())))
        {
            return this.AccessDenied();
        }

        return Ok();
    }
}

Модульное тестирование контроллера тогда так же просто, как:

[TestClass]
public class TestClass
{
    Mock<AlbumController> mockedTarget
    AlbumController target

    [TestInitialize]
    public void Init()
    {
        mockedTarget = new Mock<AlbumController>();
        target = mockedTarget.Object;
    }

    [Test]
    public void Test()
    {
        mockedTarget.Setup(x => x.CheckAccessAsync(It.IsAny<string>(),
            It.IsAny<string[]>()))
            .Returns(Task.FromResult(true));

        var result = target.Get(1);

        // Assert
    }

}

Вы всегда можете обернуть этот статический вызов в какую-то свою абстракцию:

public interface IAuthorizationService
{
    Task<bool> CheckAccessAsync(string view, string album, string id);
}

а затем есть некоторая реализация, которая делегирует вызов статическому методу расширения. Но теперь, так как вы будете работать с IAuthorizationService Вы можете свободно издеваться над CheckAccessAsync метод в ваших юнит-тестах.

Что касается тестирования реализации этой абстракции, вам, вероятно, она не нужна, поскольку она действует только как мост к классам ThinkTecture, которые уже должны быть достаточно хорошо протестированы.

Я наконец решил свою проблему. Настоящая проблема заключалась в том, что метод CheckAccess был расширением. (для моего ответа, каждый класс будет ссылаться на образец, который можно найти здесь)

Чтобы прекратить использовать метод расширения, я добавил эти методы в мой chinookAuthorization

   public Task<bool> CheckAccessAsync(ClaimsPrincipal user, string action, params string[] resources)
    {
        var ctx = new ResourceAuthorizationContext(user ?? Principal.Anonymous, action, resources);

        return CheckAccessAsync(ctx);
    }

    public Task<bool> CheckAccessAsync(ClaimsPrincipal user, IEnumerable<Claim> actions, IEnumerable<Claim> resources)
    {
        var authorizationContext = new ResourceAuthorizationContext(
            user ?? Principal.Anonymous,
            actions,
            resources);

        return CheckAccessAsync(authorizationContext);
    }    

Затем я изменил свой контроллер, чтобы иметь экземпляр ChinookAuthorization

public class AlbumController : ApiController
{
    protected readonly chinookAuthorization chinookAuth;

    public BaseApiController(chinookAuthorization chinookAuth)
    {
        if (chinookAuth == null)
            throw new ArgumentNullException("chinookAuth");

        this.chinookAuth = chinookAuth;
    }
    public async Task<IHttpActionResult> Get(int id)
    {
        if (!(await chinookAuth.CheckAccessAsync((ClaimsPrincipal)RequestContext.Principal, ChinookResources.AlbumActions.View, 
                                        ChinookResources.Album,
                                        id.ToString())))
        {
            return this.AccessDenied();
        }

        return Ok();
    }
}

И я до сих пор объявляю мою ChinookAuthorization при запуске моего owin, чтобы продолжать использовать тот же шаблон для моего вызова проверки доступа к атрибуту.

Так что теперь, я просто должен высмеивать chinookAuthorization, высмеивать ответ на вызов, чтобы вернуть true, и это все!

Другие вопросы по тегам