Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ---------------------------------------------------------
// Copyright (c) North East London ICB. All rights reserved.
// ---------------------------------------------------------

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace NHSDigital.ApiPlatform.Sdk.AspNetCore.Tests.Acceptance.Clients.ApiPlatforms
{
internal sealed class FakeSession : ISession
{
private readonly Dictionary<string, byte[]> store = new Dictionary<string, byte[]>();

public bool IsAvailable => true;
public string Id => "acceptance-session";
public IEnumerable<string> Keys => this.store.Keys;

public void Clear() => this.store.Clear();

public Task CommitAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;

public Task LoadAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;

public void Remove(string key) => this.store.Remove(key);

public void Set(string key, byte[] value) => this.store[key] = value;

public bool TryGetValue(string key, out byte[] value) => this.store.TryGetValue(key, out value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// ---------------------------------------------------------
// Copyright (c) North East London ICB. All rights reserved.
// ---------------------------------------------------------

using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using NHSDigital.ApiPlatform.Sdk.Models.Foundations.CareIdentityServices;
using Xunit;

namespace NHSDigital.ApiPlatform.Sdk.AspNetCore.Tests.Acceptance.Clients.ApiPlatforms
{
public partial class SessionBackedApiPlatformClientTests
{
[Fact]
public async Task ShouldPersistCsrfStateInTheSessionOnBuildLoginUrlAsync()
{
// given
// when
string actualLoginUrl = await this.careIdentityServiceClient.BuildLoginUrlAsync();

// then
string state = ExtractStateFromLoginUrl(actualLoginUrl);
state.Should().NotBeNullOrWhiteSpace();
this.fakeSession.Keys.Should().Contain("Nhs.ApiPlatform.CsrfState");
}

[Fact]
public async Task ShouldPersistTokensInTheSessionOnCompletingTheLoginFlowAsync()
{
// given
GivenTokenEndpointReturns(GetRandomString(), GetRandomString());
GivenUserInfoEndpointReturns(GetRandomString(), GetRandomString());
string loginUrl = await this.careIdentityServiceClient.BuildLoginUrlAsync();
string state = ExtractStateFromLoginUrl(loginUrl);

// when
await this.careIdentityServiceClient.GetUserInfoAsync(GetRandomString(), state);

// then
this.fakeSession.Keys.Should().Contain("Nhs.ApiPlatform.AccessToken");
this.fakeSession.Keys.Should().Contain("Nhs.ApiPlatform.RefreshToken");
this.fakeSession.Keys.Should().Contain("Nhs.ApiPlatform.ActiveRoleId");
}

[Fact]
public async Task ShouldReturnUserInfoOnCompletingTheLoginFlowAsync()
{
// given
string randomUserUid = GetRandomString();
string randomRoleId = GetRandomString();
GivenTokenEndpointReturns(GetRandomString(), GetRandomString());
GivenUserInfoEndpointReturns(randomUserUid, randomRoleId);
string loginUrl = await this.careIdentityServiceClient.BuildLoginUrlAsync();
string state = ExtractStateFromLoginUrl(loginUrl);

// when
NhsUserInfo actualUserInfo =
await this.careIdentityServiceClient.GetUserInfoAsync(GetRandomString(), state);

// then
actualUserInfo.NhsIdUserUid.Should().Be(randomUserUid);
actualUserInfo.NhsIdNrbacRoles.Single().PersonRoleId.Should().Be(randomRoleId);
}

[Fact]
public async Task ShouldReturnSessionStoredAccessTokenOnGetAccessTokenAsync()
{
// given
string randomAccessToken = GetRandomString();
GivenTokenEndpointReturns(randomAccessToken, GetRandomString());
GivenUserInfoEndpointReturns(GetRandomString(), GetRandomString());
string loginUrl = await this.careIdentityServiceClient.BuildLoginUrlAsync();
string state = ExtractStateFromLoginUrl(loginUrl);
await this.careIdentityServiceClient.GetUserInfoAsync(GetRandomString(), state);

// when
string actualAccessToken = await this.careIdentityServiceClient.GetAccessTokenAsync();

// then
actualAccessToken.Should().Be(randomAccessToken);
}

[Fact]
public async Task ShouldRemoveTokensFromTheSessionOnLogoutAsync()
{
// given
GivenTokenEndpointReturns(GetRandomString(), GetRandomString());
GivenUserInfoEndpointReturns(GetRandomString(), GetRandomString());
string loginUrl = await this.careIdentityServiceClient.BuildLoginUrlAsync();
string state = ExtractStateFromLoginUrl(loginUrl);
await this.careIdentityServiceClient.GetUserInfoAsync(GetRandomString(), state);

// when
await this.careIdentityServiceClient.LogoutAsync();

// then
this.fakeSession.Keys.Should().NotContain("Nhs.ApiPlatform.AccessToken");
this.fakeSession.Keys.Should().NotContain("Nhs.ApiPlatform.RefreshToken");
this.fakeSession.Keys.Should().NotContain("Nhs.ApiPlatform.ActiveRoleId");
this.fakeSession.Keys.Should().NotContain("Nhs.ApiPlatform.CsrfState");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// ---------------------------------------------------------
// Copyright (c) North East London ICB. All rights reserved.
// ---------------------------------------------------------

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using NHSDigital.ApiPlatform.Sdk.Models.Clients.Pds.Exceptions;
using NHSDigital.ApiPlatform.Sdk.Models.Foundations.Pds;
using Xunit;

namespace NHSDigital.ApiPlatform.Sdk.AspNetCore.Tests.Acceptance.Clients.ApiPlatforms
{
public partial class SessionBackedApiPlatformClientTests
{
[Fact]
public async Task ShouldSearchPatientsUsingTheSessionStoredCredentialsAsync()
{
// given
string randomNhsNumber = GetRandomNhsNumber();
string randomAccessToken = GetRandomString();
string randomRoleId = GetRandomString();
string randomPatientPayload = $"{{\"resourceType\":\"Patient\",\"id\":\"{randomNhsNumber}\"}}";
await GivenAnAuthenticatedSessionAsync(randomAccessToken, randomRoleId);
GivenPatientEndpointReturns(randomNhsNumber, randomPatientPayload);
SearchCriteria searchCriteria = CreateSearchCriteriaByNhsNumber(randomNhsNumber);

// when
string actualPayload =
await this.personalDemographicsServiceClient.SearchPatientsAsync(searchCriteria);

// then
actualPayload.Should().Be(randomPatientPayload);

var patientRequest = this.wireMockServer.LogEntries
.Last(entry => entry.RequestMessage.Path.EndsWith($"/Patient/{randomNhsNumber}"));

patientRequest.RequestMessage.Headers["Authorization"]
.Should().Contain($"Bearer {randomAccessToken}");

patientRequest.RequestMessage.Headers["NHSD-Session-URID"]
.Should().Contain(randomRoleId);
}

[Fact]
public async Task ShouldThrowValidationExceptionOnSearchPatientsIfTheSessionIsNotAuthenticatedAsync()
{
// given
SearchCriteria searchCriteria = CreateSearchCriteriaByNhsNumber(GetRandomNhsNumber());

// when
PersonalDemographicsServiceClientValidationException actualException =
await Assert.ThrowsAsync<PersonalDemographicsServiceClientValidationException>(async () =>
await this.personalDemographicsServiceClient.SearchPatientsAsync(searchCriteria));

// then
actualException.InnerException.Message
.Should().Be("Unauthorized - Unable to retrieve access token.");
}

[Fact]
public async Task ShouldThrowOperationCanceledExceptionOnSearchPatientsIfTokenIsAlreadyCancelledAsync()
{
// given
SearchCriteria searchCriteria = CreateSearchCriteriaByNhsNumber(GetRandomNhsNumber());
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();

// when
// then
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
await this.personalDemographicsServiceClient.SearchPatientsAsync(
searchCriteria,
cancellationTokenSource.Token));
}

private async Task GivenAnAuthenticatedSessionAsync(string accessToken, string roleId)
{
GivenTokenEndpointReturns(accessToken, GetRandomString());
GivenUserInfoEndpointReturns(GetRandomString(), roleId);
string loginUrl = await this.careIdentityServiceClient.BuildLoginUrlAsync();
string state = ExtractStateFromLoginUrl(loginUrl);
await this.careIdentityServiceClient.GetUserInfoAsync(GetRandomString(), state);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// ---------------------------------------------------------
// Copyright (c) North East London ICB. All rights reserved.
// ---------------------------------------------------------

using System;
using System.Net;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using NHSDigital.ApiPlatform.Sdk.Clients.ApiPlatforms;
using NHSDigital.ApiPlatform.Sdk.Models.Clients.CareIdentityService.Exceptions;
using NHSDigital.ApiPlatform.Sdk.Models.Clients.Pds.Exceptions;
using NHSDigital.ApiPlatform.Sdk.Models.Foundations.Pds;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using Xunit;

namespace NHSDigital.ApiPlatform.Sdk.AspNetCore.Tests.Acceptance.Clients.ApiPlatforms
{
public partial class SessionBackedApiPlatformClientTests
{
// Only these two tests need a short dependency timeout. Applying it to the whole class
// would leave every other test one slow HTTP call away from failing as a timeout.
private static readonly TimeSpan ShortDependencyTimeout = TimeSpan.FromSeconds(1);

[Fact]
public async Task ShouldThrowDependencyExceptionOnGetUserInfoIfTheTokenEndpointTimesOutAsync()
{
// given
this.wireMockServer
.Given(Request.Create().WithPath(TokenPath).UsingPost())
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithDelay(TimeSpan.FromSeconds(3))
.WithBody("{}"));

using ServiceProvider timeoutProvider = BuildServiceProvider(ShortDependencyTimeout);
using IServiceScope timeoutScope = timeoutProvider.CreateScope();

IApiPlatformClient timeoutClient =
timeoutScope.ServiceProvider.GetRequiredService<IApiPlatformClient>();

string loginUrl = await timeoutClient.CareIdentityServiceClient.BuildLoginUrlAsync();
string state = ExtractStateFromLoginUrl(loginUrl);

// when
CareIdentityServiceClientDependencyException actualException =
await Assert.ThrowsAsync<CareIdentityServiceClientDependencyException>(async () =>
await timeoutClient.CareIdentityServiceClient.GetUserInfoAsync(
GetRandomString(),
state));

// then
actualException.InnerException.InnerException.Should().BeOfType<TimeoutException>();

actualException.InnerException.InnerException.Message
.Should().Be("The dependency operation timed out.");
}

[Fact]
public async Task ShouldThrowDependencyExceptionOnSearchPatientsIfThePatientEndpointTimesOutAsync()
{
// given
string randomNhsNumber = GetRandomNhsNumber();
using ServiceProvider timeoutProvider = BuildServiceProvider(ShortDependencyTimeout);
using IServiceScope timeoutScope = timeoutProvider.CreateScope();

IApiPlatformClient timeoutClient =
timeoutScope.ServiceProvider.GetRequiredService<IApiPlatformClient>();

GivenTokenEndpointReturns(GetRandomString(), GetRandomString());
GivenUserInfoEndpointReturns(GetRandomString(), GetRandomString());
string loginUrl = await this.careIdentityServiceClient.BuildLoginUrlAsync();
string state = ExtractStateFromLoginUrl(loginUrl);
await this.careIdentityServiceClient.GetUserInfoAsync(GetRandomString(), state);

this.wireMockServer
.Given(Request.Create().WithPath($"{FhirPath}/Patient/{randomNhsNumber}").UsingGet())
.RespondWith(Response.Create()
.WithStatusCode(HttpStatusCode.OK)
.WithDelay(TimeSpan.FromSeconds(3))
.WithBody("{}"));

SearchCriteria searchCriteria = CreateSearchCriteriaByNhsNumber(randomNhsNumber);

// when
PersonalDemographicsServiceClientDependencyException actualException =
await Assert.ThrowsAsync<PersonalDemographicsServiceClientDependencyException>(async () =>
await timeoutClient.PersonalDemographicsServiceClient.SearchPatientsAsync(
searchCriteria));

// then
actualException.InnerException.InnerException.Should().BeOfType<TimeoutException>();
}
}
}
Loading
Loading