skip to Main Content

In our project we have a test written by a former colleague that is firing a request to an end point, which should – and does – return a 500 – InternalServerError (checking for this using Shouldly) after throwing an exception.

Code below simplified for demo purposes.

Endpoint:

[HttpGet]
[Route("machines/{idMachine:guid}/admin")]
[Authorize(Policy = Policies.RequireMachineCustomerCheck)]
public IActionResult MachineEditor(Guid idMachine)
{
    // Lookup machine...

    if (machine == null)
    {
        throw new Exception("Exception");
    }
}

Test:

[Fact]
public async Task MachineNotFoundShouldThrowException()
{
    var idMachine = Guid.NewGuid();
    var uri = new Uri($"/machines/{idMachine}/admin", UriKind.Relative);
    var httpClient = new HttpClient();

    var response = await httpClient.GetAsync(uri);
    response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
}

However, since updating from .NET Core 3.1 to .NET 6, our Azure DevOps YAML deployment pipeline is reporting the exception when running the test. It is however being thrown from a different code path, in an Authorisation policy that we have on the end point:

An unhandled exception has occurred while executing the request.
System.Exception: No machine could be found in the server’s in-memory cache for a machine ID of
7f6825f9-3e4c-4d32-aace-ec9128394e0c. Either the cache is out-of-date
or the ID is incorrect.
at Proseal.Portal.Services.Utilities.CacheHelper.GetProVisionMachine(Guid
idMachine) in
D:a1sProsealPortal.ServicesUtilitiesCacheHelper.cs:line 72
at Proseal.Portal.Server.Common.Authorisation.MachineCustomerCheckHandler.HandleRequirementAsync(AuthorizationHandlerContext
context, MachineCustomerCheck requirement) in
D:a1sProsealPortal.ServerCommonAuthorisationMachineCustomerCheck.cs:line
83
at Microsoft.AspNetCore.Authorization.AuthorizationHandler1.HandleAsync(AuthorizationHandlerContext context) at Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.AuthorizeAsync(ClaimsPrincipal user, Object resource, IEnumerable1 requirements)
at Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthorizeAsync(AuthorizationPolicy
policy, AuthenticateResult authenticationResult, HttpContext context,
Object resource)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext
context)
at Proseal.Portal.Server.Common.Middleware.AuthenticationForStaticFilesMiddleware.InvokeAsync(HttpContext
context, RequestDelegate next) in
D:a1sProsealPortal.ServerCommonMiddlewareAuthenticationForStaticFilesMiddleware.cs:line
43
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass6_1.<b__1>d.MoveNext()

Auth policy registration:

public class Startup
{
   public void ConfigureServices(IServiceCollection services)
   {
      services.AddAuthorization(options =>
      {
         options.AddPolicy(Policies.RequireMachineCustomerCheck, policy => policy.Requirements.Add(new MachineCustomerCheck()));
      }
   }
}

AuthHandler:

public class MachineCustomerCheckHandler : AuthorizationHandler<MachineCustomerCheck>
{
    protected override Task HandleRequirementAsync()
    {
        // Lookup machine

        if (machine == null)
        {
            throw new Exception("Exception");
        }
    }

Running the test locally in Visual Studio 2022’s Test Explorer is not throwing an exception or failing, but debugging it confirms the exception is thrown. Due to the 500 being returned as a result of that, the test is still passing. Not sure why the deployment pipeline is behaving differently to VS.

Wrapping the test in a try-catch was my first thought, but the exception is not being caught.

try
{
    var response = await httpClient.GetAsync(uri);
    response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
}
catch (Exception exception)
{
    exception.Message.ShouldBe("Exception");
}

After some googling I altered this, but got the same behaviour.

try
{
    await Task.Run(async () => await httpClient.GetAsync(uri));
}
catch (Exception exception)
{
    exception.Message.ShouldBe("Exception");
}

I’ve also tried using Shouldly’s ShouldThrowAsync method: https://docs.shouldly.org/documentation/exceptions/throw#shouldthrowasync, but this is also failing to catch the exception.

Func<Task> func = async () => await httpClient.GetAsync(uri);

await Should.ThrowAsync<Exception>(func);

Concerned there may be another issue at foot with the async code (and therefore the exception) being contained in a separate thread, but my async knowledge isn’t the best and so I’m coming up short on other things to try.

2

Answers


  1. Chosen as BEST ANSWER

    To fix this, I've stopped the auth handler from throwing an exception, by catching the exception and returning a 401 instead. This then allows the tests to correctly check the auth handler behaviour.

    These changes have also highlighted some issues with the way our tests have been structured, and so re-working them has now solved my problems.


  2. If you use XUnit for example, you can expect an exception like this:

     await Assert.ThrowsAsync<ExceptionType>(() => testContext.httpClient.GetAsync(uri));
    

    If you continue to get an exception that doesn’t fall into this category, check your DI. Have run into scenarios in the past where there was a DI error that was not caught/ thrown correctly.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search