< Summary

Information
Class: ArturRios.Util.Test.Functional.WebApiTest<T>
Assembly: ArturRios.Util.Test
File(s): D:\Repositories\dotnet-test-util\src\Functional\WebApiTest.cs
Line coverage
100%
Covered lines: 45
Uncovered lines: 0
Coverable lines: 45
Total lines: 111
Line coverage: 100%
Branch coverage
80%
Covered branches: 16
Total branches: 20
Branch coverage: 80%
Method coverage
100%
Covered methods: 8
Fully covered methods: 4
Total methods: 8
Method coverage: 100%
Full method coverage: 50%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
AuthenticateAsync()83.33%1212100%
Authorize(...)100%11100%
AuthenticateAndAuthorizeAsync()100%11100%
Dispose()100%11100%
Dispose(...)50%22100%
CreateProperty(...)83.33%66100%

File(s)

D:\Repositories\dotnet-test-util\src\Functional\WebApiTest.cs

#LineLine coverage
 1using System.Net;
 2using System.Reflection;
 3using ArturRios.Configuration.Enums;
 4using ArturRios.Output;
 5using ArturRios.Util.Http;
 6using ArturRios.Util.Test.Exceptions;
 7using ArturRios.Util.WebApi.Security.Records;
 8using Microsoft.AspNetCore.Mvc.Testing;
 9using Newtonsoft.Json;
 10using Newtonsoft.Json.Serialization;
 11
 12namespace ArturRios.Util.Test.Functional;
 13
 14/// <summary>
 15/// Base class for functional web API tests. It spins up an in-memory host with
 16/// <see cref="WebApplicationFactory{TEntryPoint}"/> and exposes an <see cref="HttpGateway"/> plus helpers
 17/// for authenticating and authorizing requests. Derive from it and pass the target environment.
 18/// </summary>
 19/// <typeparam name="T">The entry point type of the web API under test (typically its <c>Program</c> class).</typeparam>
 20public class WebApiTest<T> : IDisposable where T : class
 21{
 22    // HttpGateway deserializes with Newtonsoft.Json, which does not populate the protected setter of
 23    // DataOutput<T>.Data. The auth payload is therefore parsed here with a resolver that allows it.
 124    private static readonly JsonSerializerSettings AuthSerializerSettings = new()
 125    {
 126        ContractResolver = new NonPublicSetterContractResolver()
 127    };
 28
 529    private readonly WebApplicationFactory<T> _factory = new();
 30
 31    /// <summary>The gateway used to issue HTTP requests against the in-memory host.</summary>
 32    protected readonly HttpGateway Gateway;
 33
 34    /// <summary>Starts the in-memory host for the given <paramref name="environment"/> and creates the gateway.</summar
 35    /// <param name="environment">The environment the host should run as; also sets <c>ASPNETCORE_ENVIRONMENT</c>.</para
 536    protected WebApiTest(EnvironmentType environment)
 537    {
 538        Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment.ToString().ToLower());
 39
 540        Gateway = new HttpGateway(_factory.CreateClient());
 541    }
 42
 43    /// <summary>Authenticates against <paramref name="authRoute"/> and returns the resulting authentication payload.</s
 44    /// <param name="credentials">The credentials to authenticate with.</param>
 45    /// <param name="authRoute">The relative authentication route.</param>
 46    /// <returns>The authentication payload returned by the API.</returns>
 47    /// <exception cref="TestException">Thrown when authentication fails or returns no usable token.</exception>
 48    public async Task<Authentication> AuthenticateAsync(Credentials credentials, string authRoute)
 349    {
 350        var response = await Gateway.Client.PostAsync(authRoute, credentials.ToJsonStringContent());
 351        var json = await response.Content.ReadAsStringAsync();
 52
 353        var body = response.StatusCode == HttpStatusCode.OK && !string.IsNullOrEmpty(json)
 354            ? JsonConvert.DeserializeObject<DataOutput<Authentication>>(json, AuthSerializerSettings)
 355            : null;
 56
 357        var authError = body is null
 358                        || !body.Success
 359                        || body.Data is null
 360                        || string.IsNullOrEmpty(body.Data.Token);
 61
 362        return authError ? throw new TestException("Could not authenticate") : body!.Data!;
 263    }
 64
 65    /// <summary>Adds a bearer token to the gateway's default request headers.</summary>
 66    /// <param name="authToken">The JWT to send as a Bearer token.</param>
 67    public void Authorize(string authToken) =>
 268        Gateway.Client.DefaultRequestHeaders.Add("Authorization", $"Bearer {authToken}");
 69
 70    /// <summary>Authenticates and applies the resulting token to the gateway's default headers.</summary>
 71    /// <param name="credentials">The credentials to authenticate with.</param>
 72    /// <param name="authRoute">The relative authentication route.</param>
 73    public async Task AuthenticateAndAuthorizeAsync(Credentials credentials, string authRoute)
 174    {
 175        var authentication = await AuthenticateAsync(credentials, authRoute);
 76
 177        Authorize(authentication.Token!);
 178    }
 79
 80    /// <summary>Releases the in-memory host and its HTTP clients.</summary>
 81    public void Dispose()
 582    {
 583        Dispose(true);
 584        GC.SuppressFinalize(this);
 585    }
 86
 87    /// <summary>Releases managed resources. Override to dispose additional resources in derived classes.</summary>
 88    /// <param name="disposing"><c>true</c> when called from <see cref="Dispose()"/>.</param>
 89    protected virtual void Dispose(bool disposing)
 590    {
 591        if (disposing)
 592        {
 593            _factory.Dispose();
 594        }
 595    }
 96
 97    private sealed class NonPublicSetterContractResolver : DefaultContractResolver
 98    {
 99        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
 9100        {
 9101            var property = base.CreateProperty(member, memberSerialization);
 102
 9103            if (!property.Writable && member is PropertyInfo { CanWrite: true })
 1104            {
 1105                property.Writable = true;
 1106            }
 107
 9108            return property;
 9109        }
 110    }
 111}