Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DP-715 - Transferring cookie preferences to and from FTS service #1017

Merged
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,74 @@
using Moq;
using Microsoft.AspNetCore.Http;

namespace CO.CDP.OrganisationApp.Tests;

public class CookieAcceptanceMiddlewareTests
{
private readonly Mock<ICookiePreferencesService> _cookiePreferencesServiceMock;
private readonly Mock<RequestDelegate> _nextDelegateMock;
private readonly CookieAcceptanceMiddleware _middleware;
private readonly DefaultHttpContext _httpContext;

public CookieAcceptanceMiddlewareTests()
{
_cookiePreferencesServiceMock = new Mock<ICookiePreferencesService>();
_nextDelegateMock = new Mock<RequestDelegate>();
_middleware = new CookieAcceptanceMiddleware(_cookiePreferencesServiceMock.Object);
_httpContext = new DefaultHttpContext();
}

[Fact]
public async Task InvokeAsync_ShouldCallAccept_WhenQueryParameterIsTrue()
{
_httpContext.Request.QueryString = new QueryString($"?{CookieSettings.FtsHandoverParameter}=true");

await _middleware.InvokeAsync(_httpContext, _nextDelegateMock.Object);

_cookiePreferencesServiceMock.Verify(s => s.Accept(), Times.Once);
_nextDelegateMock.Verify(n => n(_httpContext), Times.Once);
}

[Fact]
public async Task InvokeAsync_ShouldCallReject_WhenQueryParameterIsFalse()
{
_httpContext.Request.QueryString = new QueryString($"?{CookieSettings.FtsHandoverParameter}=false");

await _middleware.InvokeAsync(_httpContext, _nextDelegateMock.Object);

_cookiePreferencesServiceMock.Verify(s => s.Reject(), Times.Once);
_nextDelegateMock.Verify(n => n(_httpContext), Times.Once);
}

[Fact]
public async Task InvokeAsync_ShouldCallReset_WhenQueryParameterIsUnknown()
{
_httpContext.Request.QueryString = new QueryString($"?{CookieSettings.FtsHandoverParameter}=unknown");

await _middleware.InvokeAsync(_httpContext, _nextDelegateMock.Object);

_cookiePreferencesServiceMock.Verify(s => s.Reset(), Times.Once);
_nextDelegateMock.Verify(n => n(_httpContext), Times.Once);
}

[Fact]
public async Task InvokeAsync_ShouldNotCallAnyMethod_WhenQueryParameterIsMissing()
{
await _middleware.InvokeAsync(_httpContext, _nextDelegateMock.Object);

_cookiePreferencesServiceMock.VerifyNoOtherCalls();
_nextDelegateMock.Verify(n => n(_httpContext), Times.Once);
}

[Fact]
public async Task InvokeAsync_ShouldNotCallAnyMethod_WhenQueryParameterHasInvalidValue()
{
_httpContext.Request.QueryString = new QueryString($"?{CookieSettings.FtsHandoverParameter}=invalid");

await _middleware.InvokeAsync(_httpContext, _nextDelegateMock.Object);

_cookiePreferencesServiceMock.VerifyNoOtherCalls();
_nextDelegateMock.Verify(n => n(_httpContext), Times.Once);
}
}

103 changes: 103 additions & 0 deletions Frontend/CO.CDP.OrganisationApp.Tests/CookiePreferencesServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Moq;
using FluentAssertions;
using Microsoft.AspNetCore.Http;

namespace CO.CDP.OrganisationApp.Tests;

public class CookiePreferencesServiceTests
{
private readonly Mock<IHttpContextAccessor> _httpContextAccessorMock;
private readonly Mock<HttpContext> _httpContextMock;
private readonly Mock<HttpRequest> _httpRequestMock;
private readonly Mock<HttpResponse> _httpResponseMock;
private readonly Mock<IResponseCookies> _responseCookiesMock;
private readonly Mock<IRequestCookieCollection> _requestCookiesMock;
private readonly CookiePreferencesService _cookiePreferencesService;

public CookiePreferencesServiceTests()
{
_httpContextAccessorMock = new Mock<IHttpContextAccessor>();
_httpContextMock = new Mock<HttpContext>();
_httpRequestMock = new Mock<HttpRequest>();
_httpResponseMock = new Mock<HttpResponse>();
_responseCookiesMock = new Mock<IResponseCookies>();
_requestCookiesMock = new Mock<IRequestCookieCollection>();

_httpContextMock.Setup(c => c.Request).Returns(_httpRequestMock.Object);
_httpContextMock.Setup(c => c.Response).Returns(_httpResponseMock.Object);
_httpRequestMock.Setup(r => r.Cookies).Returns(_requestCookiesMock.Object);
_httpResponseMock.Setup(r => r.Cookies).Returns(_responseCookiesMock.Object);

_httpContextAccessorMock.Setup(a => a.HttpContext).Returns(_httpContextMock.Object);

_cookiePreferencesService = new CookiePreferencesService(_httpContextAccessorMock.Object);
}

[Fact]
public void Constructor_ShouldThrowException_WhenHttpContextIsNull()
{
var accessor = new Mock<IHttpContextAccessor>();
accessor.Setup(a => a.HttpContext).Returns((HttpContext)null!);

Action act = () => new CookiePreferencesService(accessor.Object);

act.Should().Throw<InvalidOperationException>()
.WithMessage("No active HTTP context.");
}

[Fact]
public void Accept_ShouldSetCookieWithAcceptValue()
{
_cookiePreferencesService.Accept();

_responseCookiesMock.Verify(c => c.Append(CookieSettings.CookieName, "Accept", It.IsAny<CookieOptions>()), Times.Once);
_cookiePreferencesService.GetValue().Should().Be(CookieAcceptanceValues.Accept);
_cookiePreferencesService.IsAccepted().Should().BeTrue();
}

[Fact]
public void Reject_ShouldSetCookieWithRejectValue()
{
_cookiePreferencesService.Reject();

_responseCookiesMock.Verify(c => c.Append(CookieSettings.CookieName, "Reject", It.IsAny<CookieOptions>()), Times.Once);
_cookiePreferencesService.GetValue().Should().Be(CookieAcceptanceValues.Reject);
_cookiePreferencesService.IsRejected().Should().BeTrue();
}

[Fact]
public void Reset_ShouldDeleteCookie_AndSetPendingValueToUnknown()
{
_cookiePreferencesService.Reset();

_responseCookiesMock.Verify(c => c.Delete(CookieSettings.CookieName), Times.Once);
_cookiePreferencesService.GetValue().Should().Be(CookieAcceptanceValues.Unknown);
_cookiePreferencesService.IsUnknown().Should().BeTrue();
}

[Fact]
public void GetValue_ShouldReturnPendingValue_WhenSet()
{
_cookiePreferencesService.Accept();

_cookiePreferencesService.GetValue().Should().Be(CookieAcceptanceValues.Accept);
}

[Fact]
public void GetValue_ShouldReturnValueFromRequestCookie_WhenNoPendingValue()
{
_requestCookiesMock.Setup(c => c.ContainsKey(CookieSettings.CookieName)).Returns(true);
_requestCookiesMock.Setup(c => c[CookieSettings.CookieName]).Returns("Reject");

_cookiePreferencesService.GetValue().Should().Be(CookieAcceptanceValues.Reject);
}

[Fact]
public void GetValue_ShouldReturnUnknown_WhenCookieIsNotPresent()
{
_requestCookiesMock.Setup(c => c.ContainsKey(CookieSettings.CookieName)).Returns(false);

_cookiePreferencesService.GetValue().Should().Be(CookieAcceptanceValues.Unknown);
}
}

43 changes: 31 additions & 12 deletions Frontend/CO.CDP.OrganisationApp.Tests/FtsUrlServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,43 @@
using FluentAssertions;

namespace CO.CDP.OrganisationApp.Tests;

public class FtsUrlServiceTests
{
private readonly Mock<IConfiguration> _configurationMock;
private readonly FtsUrlService _service;
private readonly Mock<ICookiePreferencesService> _cookiePreferencesService;
private readonly IFtsUrlService _service;

public FtsUrlServiceTests()
{
_configurationMock = new Mock<IConfiguration>();
_cookiePreferencesService = new Mock<ICookiePreferencesService>();
_configurationMock.Setup(c => c["FtsService"]).Returns("https://example.com/");
_service = new FtsUrlService(_configurationMock.Object);
_service = new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object);
}

[Fact]
public void Constructor_ShouldThrowException_WhenFtsServiceIsNotConfigured()
{
_configurationMock.Setup(c => c["FtsService"]).Returns((string?)null);

Action action = () => new FtsUrlService(_configurationMock.Object);
Action action = () => new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object);

action.Should().Throw<InvalidOperationException>()
.WithMessage("FtsService is not configured.");
.WithMessage("FtsService is not configured.");
}

[Fact]
public void BuildUrl_ShouldTrimTrailingSlashFromBaseServiceUrl()
{
_configurationMock.Setup(c => c["FtsService"]).Returns("https://example.com/");
var service = new FtsUrlService(_configurationMock.Object);
var service = new FtsUrlService(_configurationMock.Object, _cookiePreferencesService.Object);
var endpoint = "test-endpoint";
CultureInfo.CurrentUICulture = new CultureInfo("en-GB");

var result = service.BuildUrl(endpoint);

result.Should().Be("https://example.com/test-endpoint?language=en_GB");
result.Should().Be("https://example.com/test-endpoint?language=en_GB&cookies_accepted=unknown");
}

[Fact]
Expand All @@ -48,18 +51,18 @@ public void BuildUrl_ShouldConstructCorrectUrl_WhenOnlyEndpointIsProvided()

var result = _service.BuildUrl(endpoint);

result.Should().Be("https://example.com/test-endpoint?language=en_GB");
result.Should().Be("https://example.com/test-endpoint?language=en_GB&cookies_accepted=unknown");
}

[Fact]
public void BuildUrl_ShouldConstructCorrectUrl_WhenLanguageisWelsh()
public void BuildUrl_ShouldConstructCorrectUrl_WhenLanguageIsWelsh()
{
var endpoint = "test-endpoint";
CultureInfo.CurrentUICulture = new CultureInfo("cy");

var result = _service.BuildUrl(endpoint);

result.Should().Be("https://example.com/test-endpoint?language=cy");
result.Should().Be("https://example.com/test-endpoint?language=cy&cookies_accepted=unknown");
}

[Fact]
Expand All @@ -71,7 +74,7 @@ public void BuildUrl_ShouldIncludeOrganisationId_WhenProvided()

var result = _service.BuildUrl(endpoint, organisationId);

result.Should().Be($"https://example.com/test-endpoint?language=en_GB&organisation_id={organisationId}");
result.Should().Be($"https://example.com/test-endpoint?language=en_GB&organisation_id={organisationId}&cookies_accepted=unknown");
}

[Fact]
Expand All @@ -83,19 +86,35 @@ public void BuildUrl_ShouldIncludeRedirectUrl_WhenProvided()

var result = _service.BuildUrl(endpoint, null, redirectUrl);

result.Should().Be("https://example.com/test-endpoint?language=en_GB&redirect_url=%2Fredirect-path");
result.Should().Be("https://example.com/test-endpoint?language=en_GB&redirect_url=%2Fredirect-path&cookies_accepted=unknown");
}

[Fact]
public void BuildUrl_ShouldIncludeAllParameters_WhenAllAreProvided()
{
_cookiePreferencesService.Setup(c => c.GetValue()).Returns(CookieAcceptanceValues.Accept);
var endpoint = "test-endpoint";
var organisationId = Guid.NewGuid();
var redirectUrl = "/redirect-path";
CultureInfo.CurrentUICulture = new CultureInfo("en-GB");

var result = _service.BuildUrl(endpoint, organisationId, redirectUrl);

result.Should().Be($"https://example.com/test-endpoint?language=en_GB&organisation_id={organisationId}&redirect_url=%2Fredirect-path");
result.Should().Be($"https://example.com/test-endpoint?language=en_GB&organisation_id={organisationId}&redirect_url=%2Fredirect-path&cookies_accepted=true");
}

[Theory]
[InlineData(CookieAcceptanceValues.Accept, "true")]
[InlineData(CookieAcceptanceValues.Reject, "false")]
[InlineData(CookieAcceptanceValues.Unknown, "unknown")]
public void BuildUrl_ShouldIncludeCookiesAcceptedParameter_BasedOnCookiePreferences(CookieAcceptanceValues preference, string expectedValue)
{
_cookiePreferencesService.Setup(c => c.GetValue()).Returns(preference);
var endpoint = "test-endpoint";
CultureInfo.CurrentUICulture = new CultureInfo("en-GB");

var result = _service.BuildUrl(endpoint);

result.Should().Be($"https://example.com/test-endpoint?language=en_GB&cookies_accepted={expectedValue}");
}
}
27 changes: 27 additions & 0 deletions Frontend/CO.CDP.OrganisationApp/CookieAcceptanceMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace CO.CDP.OrganisationApp;

public class CookieAcceptanceMiddleware(ICookiePreferencesService cookiePreferencesService) : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.Request.Query.TryGetValue(CookieSettings.FtsHandoverParameter, out var cookiesAcceptedValue))
{
string cookiesAccepted = cookiesAcceptedValue.ToString().ToLower();

switch (cookiesAccepted)
{
case "true":
cookiePreferencesService.Accept();
break;
case "false":
cookiePreferencesService.Reject();
break;
case "unknown":
cookiePreferencesService.Reset();
break;
}
}

await next(context);
}
}
Loading
Loading