< Summary

Information
Class: ArturRios.Util.Test.Functional.WebApiTest<T>
Assembly: ArturRios.Util.Test
File(s): /home/runner/work/dotnet-test-util/dotnet-test-util/src/Functional/WebApiTest.cs
Line coverage
100%
Covered lines: 26
Uncovered lines: 0
Coverable lines: 26
Total lines: 102
Line coverage: 100%
Branch coverage
91%
Covered branches: 11
Total branches: 12
Branch coverage: 91.6%
Method coverage
100%
Covered methods: 6
Fully covered methods: 6
Total methods: 6
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
AuthenticateAsync()100%1010100%
Authorize(...)100%11100%
AuthenticateAndAuthorizeAsync()100%11100%
Dispose()100%11100%
Dispose(...)50%22100%

File(s)

/home/runner/work/dotnet-test-util/dotnet-test-util/src/Functional/WebApiTest.cs

#LineLine coverage
 1using System.Net.Http.Headers;
 2using ArturRios.Configuration.Enums;
 3using ArturRios.Output;
 4using ArturRios.Util.Http;
 5using ArturRios.Util.Test.Exceptions;
 6using ArturRios.Util.WebApi.Security.Records;
 7using Microsoft.AspNetCore.Hosting;
 8using Microsoft.AspNetCore.Mvc.Testing;
 9
 10namespace ArturRios.Util.Test.Functional;
 11
 12/// <summary>
 13/// Base class for functional web API tests. It spins up an in-memory host with
 14/// <see cref="WebApplicationFactory{TEntryPoint}"/> and exposes an <see cref="HttpGateway"/> plus helpers
 15/// for authenticating and authorizing requests. Derive from it and pass the target environment.
 16/// </summary>
 17/// <typeparam name="T">The entry point type of the web API under test (typically its <c>Program</c> class).</typeparam>
 18/// <remarks>
 19/// The constructor also sets the process-wide <c>ASPNETCORE_ENVIRONMENT</c> variable, because that is what
 20/// the environment-aware test attributes read. Two test classes deriving from this with different
 21/// environments therefore cannot run in parallel; put them in one xUnit collection when that comes up.
 22/// </remarks>
 23public class WebApiTest<T> : IDisposable where T : class
 24{
 25    private readonly WebApplicationFactory<T> _factory;
 26
 27    /// <summary>The gateway used to issue HTTP requests against the in-memory host.</summary>
 28    protected readonly HttpGateway Gateway;
 29
 30    /// <summary>Starts the in-memory host for the given <paramref name="environment"/> and creates the gateway.</summar
 31    /// <param name="environment">The environment the host should run as; also sets <c>ASPNETCORE_ENVIRONMENT</c>.</para
 532    protected WebApiTest(EnvironmentType environment)
 33    {
 534        var environmentName = environment.ToString().ToLowerInvariant();
 35
 536        Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environmentName);
 37
 38        // Told to the host directly as well as through the variable, so the host's environment does not
 39        // depend on whether anything else in the process has since changed it.
 540        _factory = new WebApplicationFactory<T>()
 1041            .WithWebHostBuilder(builder => builder.UseEnvironment(environmentName));
 42
 543        Gateway = new HttpGateway(_factory.CreateClient());
 544    }
 45
 46    /// <summary>Authenticates against <paramref name="authRoute"/> and returns the resulting authentication payload.</s
 47    /// <param name="credentials">The credentials to authenticate with.</param>
 48    /// <param name="authRoute">The relative authentication route.</param>
 49    /// <returns>The authentication payload returned by the API.</returns>
 50    /// <exception cref="TestException">Thrown when authentication fails or returns no usable token.</exception>
 51    public async Task<Authentication> AuthenticateAsync(Credentials credentials, string authRoute)
 52    {
 353        var response = await Gateway.PostAsync<DataOutput<Authentication>>(authRoute, credentials);
 54
 355        var body = response.Body;
 56
 357        var authError = !response.IsSuccess
 358                        || body is null
 359                        || !body.Success
 360                        || body.Data is null
 361                        || string.IsNullOrEmpty(body.Data.Token);
 62
 363        return authError ? throw new TestException("Could not authenticate") : body!.Data!;
 264    }
 65
 66    /// <summary>Sets the bearer token on the gateway's default request headers, replacing any previous one.</summary>
 67    /// <param name="authToken">The JWT to send as a Bearer token.</param>
 68    /// <remarks>
 69    /// Assigning the header rather than adding it makes this idempotent: calling it twice replaces the
 70    /// token instead of failing on a duplicate <c>Authorization</c> header, which is what
 71    /// <c>BaseWebApiClientRoute.Authorize</c> already did.
 72    /// </remarks>
 73    public void Authorize(string authToken) =>
 274        Gateway.Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
 75
 76    /// <summary>Authenticates and applies the resulting token to the gateway's default headers.</summary>
 77    /// <param name="credentials">The credentials to authenticate with.</param>
 78    /// <param name="authRoute">The relative authentication route.</param>
 79    public async Task AuthenticateAndAuthorizeAsync(Credentials credentials, string authRoute)
 80    {
 181        var authentication = await AuthenticateAsync(credentials, authRoute);
 82
 183        Authorize(authentication.Token!);
 184    }
 85
 86    /// <summary>Releases the in-memory host and its HTTP clients.</summary>
 87    public void Dispose()
 88    {
 589        Dispose(true);
 590        GC.SuppressFinalize(this);
 591    }
 92
 93    /// <summary>Releases managed resources. Override to dispose additional resources in derived classes.</summary>
 94    /// <param name="disposing"><c>true</c> when called from <see cref="Dispose()"/>.</param>
 95    protected virtual void Dispose(bool disposing)
 96    {
 597        if (disposing)
 98        {
 599            _factory.Dispose();
 100        }
 5101    }
 102}