API Integration Test for Login controller using Identity server4 in asp.net core

3

I am having issues with testing Login Controller using IdentityServer4. It throws the following error:

{System.Net.Http.WinHttpException (0x80072EFD): A connection with the server could not be established

I am trying to generate the access Token using ResourceOwnerPassword, for which I have implemented IResourceOwnerPasswordValidator. I get the error in UserAccessToken.cs class when I call the RequestResourcePasswordAsync. I am pretty sure it is because of the handler. Because if I use a handler in my test class and call the TokenClient with that handler I do get access Token but then I cannot test my Login Controller.

LoginController.cs

[HttpPost]
public async Task<IActionResult> Login([FromBody]LoginViewModel user)
{       
    var accessToken = await UserAccessToken.GenerateTokenAsync(user.Username, user.Password);
    var loginToken = JsonConvert.DeserializeObject(accessToken);
    return Ok(loginToken); 
}

UserAccessToken.cs

public async Task<string> GenerateTokenAsync(string username, string password)
        {
            var tokenUrl = "http://localhost:5000/connect/token";
            var tokenClient = new TokenClient(tokenUrl,"ClientId","ClientPassword");
            var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
            if (tokenResponse.IsError)
            {
                throw new AuthenticationFailedException(tokenResponse.Error);
            }
            return tokenResponse.Json.ToString();

        }

TestClass.cs

 [Fact]
 public async Task Login()
 {          
     var client = _identityServer.CreateClient();
     var data = new StringContent(JsonConvert.SerializeObject(new LoginViewModel { Username = "1206", Password = "5m{F?Hk92/Qj}n7Lp6" }), Encoding.UTF8, "application/json");
     var dd = await client.PostAsync("http://localhost:5000/login", data);
     var ss = dd;
 }

IdentityServerSetup.cs //Integration Test Setup

public class IdentityServerSetup
{
    private TestServer _identityServer;
    private const string TokenEndpoint = "http://localhost:5000/connect/token";
    public HttpMessageHandler _handler;

    //IF I use this code I do get a AccessToken
    public async Task<string> GetAccessTokenForUser(string userName, string password, string clientId, string clientSecret, string apiName = "integrapay.api.public")
    {
        var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: _handler);
        var response = await client.RequestResourceOwnerPasswordAsync(userName, password, apiName);
        return response.AccessToken;    
    }
}
c#
asp.net-core-2.0
identityserver4
asked on Stack Overflow Jan 8, 2018 by maxspan • edited Jan 8, 2018 by GeorgDangl

1 Answer

4

Well, you have already answered the question yourself: The problem is with the HttpHandler the TokenClient uses. It should use the one provided by the TestServer to successfully communicate with it instead of doing actual requests to localhost.

Right now, UserAccessToken requires a TokenClient. This is a dependency of your class, so you should refactor the code to pass in a TokenClient instead of generating it yourself. This pattern is called Dependency Injection and is ideal for cases like yours, where you might have different requirements in your tests than in your production setup.

You could make the code look like this:

UserAccessToken.cs

public class UserAccessToken
{
    private readonly TokenClient _tokenClient;

    public UserAccessToken(TokenClient tokenClient)
    {
        _tokenClient = tokenClient;
    }

    public async Task<string> GenerateTokenAsync(string username, string password)
    {
        var tokenUrl = "http://localhost:5000/connect/token";
        var tokenResponse = await _tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
        if (tokenResponse.IsError)
        {
            throw new AuthenticationFailedException(tokenResponse.Error);
        }
        return tokenResponse.Json.ToString();
    }
}

TestHelpers.cs

public static class TestHelpers
{
    private static TestServer _testServer;
    private static readonly object _initializationLock = new object();

    public static TestServer GetTestServer()
    {
        if (_testServer == null)
        {
            InitializeTestServer();
        }
        return _testServer;
    }

    private static void InitializeTestServer()
    {
        lock (_initializationLock)
        {
            if (_testServer != null)
            {
                return;
            }
            var webHostBuilder = new WebHostBuilder()
                .UseStartup<IntegrationTestsStartup>();
            var testServer = new TestServer(webHostBuilder);
            var initializationTask = InitializeDatabase(testServer);
            initializationTask.ConfigureAwait(false);
            initializationTask.Wait();
            testServer.BaseAddress = new Uri("http://localhost");
            _testServer = testServer;
        }
    }
}

IntegrationTestsStartup.cs

public class IntegrationTestsStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<TokenClient>(() =>
        {
            var handler = TestUtilities.GetTestServer().CreateHandler();
            var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: handler);
            return client;
        };
        services.AddTransient<UserAccessToken>();
    }
}

LoginController.cs

public class LoginController : Controller
{
    private readonly UserAccessToken _userAccessToken;

    public LoginController(UserAccessToken userAccessToken)
    {
        _userAccessToken = userAccessToken;
    }

    [HttpPost]
    public async Task<IActionResult> Login([FromBody]LoginViewModel user)
    {
        var accessToken = await _userAccessToken .GenerateTokenAsync(user.Username, user.Password);
        var loginToken = JsonConvert.DeserializeObject(accessToken);
        return Ok(loginToken);
    }
}

Here's one of my GitHub projects that makes use of the TestServer class and shows how I'm using it. It's not using IdentityServer4, though.

answered on Stack Overflow Jan 8, 2018 by GeorgDangl

User contributions licensed under CC BY-SA 3.0