About Azure AD authorization Integration Tests
Tags: azure, testing, authorization, dotnet
Published: 2021-12-06 10:09:32

#Introduction

It is obvious thing that during software creation life-cycle the code quality and proper functionality are the must. Besides of coding techniques like code reviews, Merge Requests/Pull Requests, pair programming, etc. various testing approaches are the must also. The **QA** teams or **UAT** tests are responsible for it at the end of coding process, but before it, the ones, who responsible for code quality are,
of course, developers.

An outcome of development is a “product” — properly working peace of software and in the era of micro-services, teams can decide about development approaches (_Test-Driven-Development_, _Behavioral-Driven Development_, _Acceptance Test-Driven Development_, etc.).

The “basic unit” for testing code is _unit test_. Coding techniques have changed during last years to make possible to cover you code (methods in a classes or units of code) by unit tests. Dependency injection and IoC containers is a “standard” (the techniques become more and more similar in **.NET** and **Java** ecosystems) while it makes also possible convenient unit testing.
Next step, is interaction testing of software components in a bigger functionality. For example, _REST API_ method that reads data from another _REST_ or _asynchronously_ writes it to some _Message Broker_ or Database. All methods used in this “process” are covered by Unit Tests, but such overall “cooperation” them starting from API entry point are not. And here developers start to write _Integration Tests_ (or Integration Tests are already written, if you use `TDD` or `BDD`).
_End to End tests_ (`E2E`) — are the next degree of your software functional tests and it is very useful if they are automated during `CI/CD` pipelines execution. But, of course, it depends :).
Load and stress testing that is another type of non functional tests that characterize the quality of you software. While it is not a part of this post let me redirect you [here](https://docs.microsoft.com/en-us/aspnet/core/test/load-tests?view=aspnetcore-5.0).

And a little bit more about **Unit** vs **Integration** vs **System** vs **E2E** Testing comparison [from MS](https://microsoft.github.io/code-with-engineering-playbook/automated-testing/e2e-testing/testing-comparison/).

#Azure AD authorization integration tests

Here I concentrate on integration tests of Web API example with Azure AD authorization, authentication and how to arrange it in a simple and readable way.

Lets imagine that we have CRUD API controller for some user notes for abstract documents. It is simply a title and description for document that user stores, something like this:

```cs
namespace Compentio.Ferragosto.Api.Controllers
{
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Identity.Web.Resource;
    using Compentio.Ferragosto.Notes;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using System;

    [Authorize]
    [ApiController]
    [ApiConventionType(typeof(DefaultApiConventions))]
    [RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
    [Route("api/notes")]
    public class NotesController : ControllerBase
    {
        private readonly INotesService _notesService;

        public NotesController(INotesService notesService)
        { 
            _notesService = notesService;
        }

        /// <summary>
        /// Returns list of notes
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<IEnumerable<Note>> Get()
        {
            return await _notesService.GetNotes().ConfigureAwait(false);
        }

        /// <summary>
        /// Returns note by its id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("{id}")]
        [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Get))]
        public async Task<Note> GetNote(string id)
        {
            return await _notesService.GetNote(id).ConfigureAwait(false);
        }

        /// <summary>
        /// Method adds new note
        /// </summary>
        /// <param name="note"></param>
        /// <returns></returns>
        [HttpPost()]
        [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Post))]
        public async Task<ActionResult<Note>> Add([FromBody] Note note)
        {
            return await _notesService.AddNote(note).ConfigureAwait(false);
        }

        /// <summary>
        /// Method updates the note
        /// </summary>
        /// <param name="id"></param>
        /// <param name="note"></param>
        /// <returns></returns>
        [HttpPut("{id}")]
        [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Put))]
        public async Task<ActionResult<Note>> Update(string id, [FromBody] Note note)
        {
            var noteToUpdate = await _notesService.GetNote(id).ConfigureAwait(false);
            if (noteToUpdate == null)
            {
                return NotFound();
            }

            var updatedNote = await _notesService.UpdateNote(note).ConfigureAwait(false);
            return Accepted(updatedNote);
        }

        /// <summary>
        /// Method deletes the note
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete("{id}")]
        [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Delete))]
        public async Task<IActionResult> Delete(string id)
        {
            await _notesService.DeleteNote(id).ConfigureAwait(false);
            return Accepted();
        }
    }
}
```
The Azure Ad Scopes configured for _access_as_user_ and _access_as_admin_. Web API applications here consists of three tiers: `NotesControler`, `NotesService` and `NotesRepository` with **MongoDB** configured underneath.

The overall code you can find on the GitHub: [Compentio.Ferragosto.Notes](https://github.com/alekshura/Compentio.Ferragosto.Notes)
The integration tests for getting notes method could be like this:

```cs
public class NotesApiIntegrationTests : IClassFixture<WebApplicationFactory<Api.Startup>>
{
	private readonly WebApplicationFactory<Api.Startup> _factory;
	private readonly NotesRepositoryMock _notesRepositoryMock;

	private const string notesBaseUrl = "api/notes";

	public NotesApiIntegrationTests(WebApplicationFactory<Api.Startup> factory)
	{
		_factory = factory;
		_notesRepositoryMock = new();
	}


	[Fact]
	public async Task ShouldReturnListOfNotes()
	{
		// Arrange
		var httpClient = _factory.WithAuthentication()
				  .WithService(_ => _notesRepositoryMock.Object)
				  .CreateAndConfigureClient();

		var mockedNotes = NotesMocks.Notes;
		_notesRepositoryMock.MockGetNotes(mockedNotes);

		// Act
		var response = await httpClient.GetAsync(notesBaseUrl).ConfigureAwait(false);
		var notes = await response.Content.ReadFromJsonAsync<IEnumerable<Note>>().ConfigureAwait(false);

		// Assert
		response.StatusCode.Should().Be(HttpStatusCode.OK);
		response.Content.Headers.ContentType.ToString().Should().ContainAll("application/json; charset=utf-8");
		notes.Should().BeEquivalentTo(mockedNotes);            
	}

	[Fact]
	public async Task ShouldBeUnauthorized()
	{
		// Arrange
		var httpClient = _factory.WithService(_ => _notesRepositoryMock.Object)
				  .CreateAndConfigureClient();


		// Act
		var response = await httpClient.GetAsync(notesBaseUrl).ConfigureAwait(false);

		// Assert
		response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
		_notesRepositoryMock.Verify(notes => notes.GetNotes(), Times.Never());
	}

	[Fact]
	public async Task ShouldBeForbidden()
	{
		// Arrange
		var httpClient = _factory.WithAuthenticationWithoutClaims()
				  .WithService(_ => _notesRepositoryMock.Object)
				  .CreateAndConfigureClient();


		// Act
		Func<Task> getNotesTask = async () => { _ = await httpClient.GetAsync(notesBaseUrl).ConfigureAwait(false); };

		// Assert
		await getNotesTask.Should().ThrowAsync<System.Net.Http.HttpRequestException>();

		_notesRepositoryMock.Verify(notes => notes.GetNotes(), Times.Never());
	}
}
```

Few things here we should note:

- `WebApplicationFactory` — factory for bootstrapping an application in memory for functional and integration tests
- `_httpClient` — HTTP client created using WebApplicationFactory extensions for authentication and mocking services — code snippets are below
- `_notesRepositoryMock` — mocks for repository.

`WebApplicationFactory` boostraps in memory HTTP web server, but for our scenario with authorization and authentication that’s not enough cause we need to check proper and invalid (unauthorized and unauthenticated) requests to our API. To cover it I’ve added `WithAuthentication()` and `WithAuthenticationWithoutClaims()` extensions methods for factory:

```cs
public static class WebApplicationFactoryExtensions
{
	public static WebApplicationFactory<T> WithAuthentication<T>(this WebApplicationFactory<T> factory) where T : class
	{
		return AddAuthentication<T, TestAuthenticationHandler>(factory);
	}

	public static WebApplicationFactory<T> WithAuthenticationWithoutClaims<T>(this WebApplicationFactory<T> factory) where T : class
	{
		return AddAuthentication<T, TestAuthenticationHandlerWithoutClaims>(factory);
	}

	private static WebApplicationFactory<T> AddAuthentication<T, TAuthHandler>(this WebApplicationFactory<T> factory) 
		where T : class
		where TAuthHandler : TestAuthenticationHandlerBase
	{
		return factory.WithWebHostBuilder(builder =>
		{
			builder.ConfigureTestServices(services =>
			{
				services.AddAuthentication("Test")
					.AddScheme<AuthenticationSchemeOptions, TAuthHandler>("Test", options => { });
			});
		});
	}

	public static WebApplicationFactory<TFactory> WithService<TFactory, TService>(this WebApplicationFactory<TFactory> factory, Func<IServiceProvider, TService> implementationFactory) 
		where TService : class 
		where TFactory : class
	{
		return factory.WithWebHostBuilder(builder =>
		{
			builder.ConfigureTestServices(services =>
			{
				services.AddTransient(implementationFactory);
			});
		});
	}

	public static HttpClient CreateAndConfigureClient<T>(this WebApplicationFactory<T> factory) where T : class
	{
		var client = factory.CreateClient(new WebApplicationFactoryClientOptions
		{
			AllowAutoRedirect = false
		});

		client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");

		return client;
	}

}
```
These methods use different `AuthenticationHandlers`: `TestAuthenticationHandler` for proper authentication and `TestAuthenticationHandlerWithoutClaims` for user, that does not have required claims. The handlers are below:

```cs
public class TestAuthenticationHandler : TestAuthenticationHandlerBase
{
	public TestAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
		ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
		: base(options, logger, encoder, clock)
	{
	}

	protected override Claim[] CreateClaims() => new[]
	{
		new Claim(ClaimTypes.Name, "Test user"),
		new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
		new Claim("http://schemas.microsoft.com/identity/claims/scope", "access_as_user")
	};
}
```

and `TestAuthenticationHandlerWithoutClaims` looks like:


```cs
public class TestAuthenticationHandlerWithoutClaims : TestAuthenticationHandlerBase
{
	public TestAuthenticationHandlerWithoutClaims(IOptionsMonitor<AuthenticationSchemeOptions> options,
		ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
		: base(options, logger, encoder, clock)
	{
	}

	protected override Claim[] CreateClaims() => new[]
	{
		new Claim(ClaimTypes.Name, "Test user")
	};
}
```
and `TestAuthenticationHandlerBase` is:

```cs

public abstract class TestAuthenticationHandlerBase : AuthenticationHandler<AuthenticationSchemeOptions>
{
	protected TestAuthenticationHandlerBase(IOptionsMonitor<AuthenticationSchemeOptions> options,
		ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
		: base(options, logger, encoder, clock)
	{
	}

	protected abstract Claim[] CreateClaims();

	protected override Task<AuthenticateResult> HandleAuthenticateAsync()
	{
		var claims = CreateClaims();  

		var identity = new ClaimsIdentity(claims, "Test");
		var principal = new ClaimsPrincipal(identity);
		var ticket = new AuthenticationTicket(principal, "Test");

		var result = AuthenticateResult.Success(ticket);
		return Task.FromResult(result);
	}
}
```
here it is important in `TestAuthenticationHandler` to use claim type as **_“http://schemas.microsoft.com/identity/claims/scope”_** for Azure AD `access_as_user scope`. In the `TestAuthenticationHandlerWithoutClaims` we do not add required in `NotesController` `access_as_user` scope and in test case `ShouldBeForbidden` (in `NotesApiIntegrationTests`) we expect **Forbidden** response.
`ShouldBeUnauthorized()` method does not configured to use any authentication handler, thus we expect **Unauthorized** _401_ error and, obviously, no any interaction with `_notesRepository`:

```cs
// Arrange
var httpClient = _factory.WithService(_ => _notesRepositoryMock.Object)
.CreateAndConfigureClient();
// Act
var response = await httpClient.GetAsync(notesBaseUrl).ConfigureAwait(false);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
_notesRepositoryMock.Verify(notes => notes.GetNotes(), Times.Never());
```
# Summary
Here I've covered three basic auth scenarios for API requests of one GET method: 
- _200_ **OK**, 
- _401_ **Unauthorized** 
- _403_ **Forbidden**. 
From my experience, it is important always to keep such tests in code and execute them during `CI/CD` pipelines, since it prevents us from accidental (which, unfortunately, occurs often) removal of Authorize or RequiredScope controller attributes.
Obviously, in a real web apps integration testing scenarios, more test needed: e.g. validation tests (_BadRequest_), _Server unexpected errors_ (500), empty lists or NotFound for one entity, ect.

In the post I’d like to show how we use WebApplicationFactory to arrange integration tests for different authorization scenarios. For testing and mocks I’ve used:
- xUnit
- Moq
- Fluent Assertions
- Bogus
For mocking also I propose to use “Fluent Mocking” like this:

```cs
public class NotesServiceMock : Mock<INotesService>
{
	public NotesServiceMock MockGetNotes(IEnumerable<Note> notes)
	{
		Setup(service => service.GetNotes()).ReturnsAsync(notes);
		return this;
	}

	public NotesServiceMock MockGetNote(string noteId, Note note)
	{
		Setup(service => service.GetNote(It.Is<string>(i => i == noteId))).ReturnsAsync(note);
		return this;
	}

	public NotesServiceMock MockAddNote(Note note)
	{
		note.Id = Guid.NewGuid().ToString();
		Setup(service => service.AddNote(note)).ReturnsAsync(note);
		return this;
	}

	public NotesServiceMock MockModifyNote(Note note)
	{
		Setup(service => service.GetNote(It.Is<string>(i => i == note.Id))).ReturnsAsync(note);
		return this;
	}
}
```

Thank you and pleasure coding!