How can I get my ServiceStack Tests to authenticat

2019-07-21 12:24发布

问题:

I've got a working implementation of CustomCredentialsAuth implemented in my ServiceStack app. I can hit the URL with auth credentials, and it works as expected.

In my tests however, I'm not having the same luck.

I'm using RestSharp, and if I disable [Authenticate], I can get all of my tests to pass.

Enabling [Authenticate] and running the tests give me

Expected: OK
But was: Unauthorized

Here is my test. How can I get RestSharp to authenticate for my tests?

using System;
using System.Net;
using FutureState.AppCore.Tests.Integration.Repositories.Fixtures;
using NUnit.Framework;
using RestSharp;

namespace FutureState.AppCore.Tests.Functional.Services
{
    [TestFixture]
    public class UserServiceInterfaceTests
    {
        private RestSchemaValidator _restSchemaValidator;
        private string _testLoginEmail;
        private string _testLoginPassword;

        [SetUp]
        public void SetUp ()
        {
            _restSchemaValidator = new RestSchemaValidator();
            _testLoginEmail = UserFixture.SystemAccount.Email;
            _testLoginPassword = "password";

        }

        [Test]
        public void ShouldGetAListOfUsersAndReturnStatusOk ()
        {
                // Setup
                var client = new RestClient( ServiceTestAppHostBase.BaseUrl );
                client.Authenticator = new HttpBasicAuthenticator( _testLoginEmail, _testLoginPassword );
                var request = new RestRequest( "/users/", Method.GET ) { RequestFormat = DataFormat.Json };

                // Execute
                var response = client.Execute( request );

                // Assert
                Assert.That( response.ErrorMessage, Is.Null );
                Assert.That( response.StatusCode, Is.EqualTo( HttpStatusCode.OK ) );
                _restSchemaValidator.ValidateResponse( "ExpectedUsersResponse.json", response.Content );
        }

        [Test]
        public void ShouldGetAUserAndReturnStatusOk ()
        {
            // Setup
            var expectedUserId = new Guid( UserFixture.FirstUserId );
            var client = new RestClient( ServiceTestAppHostBase.BaseUrl );
            client.Authenticator = new HttpBasicAuthenticator( _testLoginEmail, _testLoginPassword );
            var request = new RestRequest( "/users/" + expectedUserId, Method.GET ) { RequestFormat = DataFormat.Json };

            // Execute
            var response = client.Execute( request );

            // Assert
            Assert.That( response.ErrorMessage, Is.Null );
            Assert.That( response.StatusCode, Is.EqualTo( HttpStatusCode.OK ) );
            _restSchemaValidator.ValidateResponse( "ExpectedUserResponse.json", response.Content );
        }
    }
}

I'm using a custom auth provider:

public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
    private readonly IUserService _userService;
    private Guid _userId;

    public CustomCredentialsAuthProvider ( Container container )
    {
        _userService = container.Resolve<IUserService>();
    }

    public override bool TryAuthenticate ( IServiceBase authService, string email, string password )
    {
        var user = _userService.GetByEmailAddress( email );
        user.Password = password; // Add the posted password to the user object before authenticating.

        _userId = user.Id;
        return _userService.CheckPassword( user );
    }

    public override void OnAuthenticated ( IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo )
    {
        session.Id = _userId.ToString();

        //Important: You need to save the session!
        authService.SaveSession( session, SessionExpiry );
    }
}

And my TestAppHostBase wires up the auth like this.

private void ConfigureAuth ( Container container )
{

    //Default route: /auth/{provider}
    Plugins.Add( new AuthFeature( () => new AuthUserSession(),
     new IAuthProvider[] {
         new CustomCredentialsAuthProvider(container)
     } ) );

    //Default route: /register
    Plugins.Add( new RegistrationFeature() );

}

Further development, calling the following code DOES return true for the user, but obviously doesn't pass the session data to the subsequent RestRequest.

// Calling this returns TRUE for TryAuthenticate
// But doesn't retain the session data for the subsequenet request.
var container = EndpointHost.AppHost.TryResolve<Container>();
var authService = new AuthService();
var customCredentialsAuthProvider = new CustomCredentialsAuthProvider( container );
customCredentialsAuthProvider.TryAuthenticate(authService, _testLoginEmail, _testLoginPassword);

回答1:

So it turns out the best way we could come up with to solve this is to use a CookieContainer and pass it as a part of the client.

First we made a base class for our ServiceInterfaceTests

public class ServiceInterfaceTestBase
{
    protected  IRestClient Client;
    protected void AuthenticateClient(string email, string password)
    {

        Client = new RestClient( ServiceTestAppHostBase.BaseUrl );
        var login = new RestRequest( "/auth", Method.POST );
        login.AddParameter( "username", email );
        login.AddParameter( "password", password );

        var response = Client.Execute( login );
        var cookieJar = new CookieContainer();

        if ( response.StatusCode == HttpStatusCode.OK )
        {
            var cookie = response.Cookies.FirstOrDefault();
            cookieJar.Add( new Cookie( cookie.Name, cookie.Value, cookie.Path, cookie.Domain ) );
        }

        Client.CookieContainer = cookieJar;  
    }
}

The ServiceInterfaceTests inherit from it

[TestFixture]
public class UserServiceInterfaceTests : ServiceInterfaceTestBase
{

Then in our Setup, we call the auth method.

[SetUp]
public void SetUp ()
{
    _restSchemaValidator = new RestSchemaValidator();
    _testLoginEmail = UserFixture.SystemAccount.Email;
    _testLoginPassword = "password"; // the database contains a hashed password version of "password".

  AuthenticateClient(_testLoginEmail, _testLoginPassword);
}

And lastly our Test will look like

[Test]
public void ShouldGetAListOfUsersAndReturnStatusOk ()
{
    // Setup
    var request = new RestRequest( "/users/", Method.GET ) { RequestFormat = DataFormat.Json, };

    // Execute
    var response = Client.Execute( request );

    // Assert
    Assert.That( response.ErrorMessage, Is.Null );
    Assert.That( response.StatusCode, Is.EqualTo( HttpStatusCode.OK ) );
    _restSchemaValidator.ValidateResponse( "ExpectedUsersResponse.json", response.Content );
    Trace.Write( response.Content );
}