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-980 - Escape values going through notification banner link #1019

Merged
merged 10 commits into from
Dec 10, 2024
Merged
182 changes: 182 additions & 0 deletions Frontend/CO.CDP.OrganisationApp.Tests/FlashMessageServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using CO.CDP.OrganisationApp.Constants;
using CO.CDP.OrganisationApp.Models;
using Moq;

namespace CO.CDP.OrganisationApp.Tests;
public class FlashMessageServiceTests
{
private readonly Mock<ITempDataService> _tempDataServiceMock;
private readonly FlashMessageService _flashMessageService;

public FlashMessageServiceTests()
{
_tempDataServiceMock = new Mock<ITempDataService>();
_flashMessageService = new FlashMessageService(_tempDataServiceMock.Object);
}

[Fact]
public void SetSuccessMessage_ShouldCallTempDataServiceWithSuccessMessageType()
{
var heading = "Test success Heading";
var description = "Test success Description";

_flashMessageService.SetSuccessMessage(heading, description);

_tempDataServiceMock.Verify(s =>
s.Put(FlashMessageTypes.Success,
It.Is<FlashMessage>(m => m.Heading == heading && m.Description == description)),
Times.Once);
}

[Fact]
public void SetSuccessMessage_ShouldCallTempDataServiceWithTitle()
{
var heading = "Test success Heading";
var description = "Test success Description";
var title = "Test success Title";

_flashMessageService.SetSuccessMessage(heading, description, title);

_tempDataServiceMock.Verify(s =>
s.Put(FlashMessageTypes.Success,
It.Is<FlashMessage>(m => m.Heading == heading && m.Description == description && m.Title == title)),
Times.Once);
}

[Fact]
public void SetImportantMessage_ShouldCallTempDataServiceWithImportantMessageType()
{
var heading = "Test important Heading";
var description = "Test important Description";

_flashMessageService.SetImportantMessage(heading, description);

_tempDataServiceMock.Verify(s =>
s.Put(FlashMessageTypes.Important,
It.Is<FlashMessage>(m => m.Heading == heading && m.Description == description)),
Times.Once);
}

[Fact]
public void GetFlashMessage_ShouldReturnMessageFromTempDataService()
{
var messageType = FlashMessageTypes.Success;
var expectedMessage = new FlashMessage("Test Heading", "Test Description");
_tempDataServiceMock.Setup(s => s.Get<FlashMessage>(messageType))
.Returns(expectedMessage);

var result = _flashMessageService.GetFlashMessage(messageType);

Assert.Equal(expectedMessage.Heading, result!.Heading);
Assert.Equal(expectedMessage.Description, result.Description);
_tempDataServiceMock.Verify(s => s.Get<FlashMessage>(messageType), Times.Once);
}

[Fact]
public void PeekFlashMessage_ShouldReturnMessageFromTempDataServiceWithoutRemovingIt()
{
var messageType = FlashMessageTypes.Important;
var expectedMessage = new FlashMessage("Test Heading", "Test Description");
_tempDataServiceMock.Setup(s => s.Peek<FlashMessage>(messageType))
.Returns(expectedMessage);

var result = _flashMessageService.PeekFlashMessage(messageType);

Assert.Equal(expectedMessage.Heading, result!.Heading);
Assert.Equal(expectedMessage.Description, result.Description);
_tempDataServiceMock.Verify(s => s.Peek<FlashMessage>(messageType), Times.Once);
}

[Fact]
public void GetFlashMessage_ShouldReturnNull_WhenMessageDoesNotExist()
{
var messageType = FlashMessageTypes.Success;
_tempDataServiceMock.Setup(s => s.Get<FlashMessage>(messageType))
.Returns((FlashMessage?)null);

var result = _flashMessageService.GetFlashMessage(messageType);

Assert.Null(result);
_tempDataServiceMock.Verify(s => s.Get<FlashMessage>(messageType), Times.Once);
}

[Fact]
public void PeekFlashMessage_ShouldReturnNull_WhenMessageDoesNotExist()
{
var messageType = FlashMessageTypes.Important;
_tempDataServiceMock.Setup(s => s.Peek<FlashMessage>(messageType))
.Returns((FlashMessage?)null);

var result = _flashMessageService.PeekFlashMessage(messageType);

Assert.Null(result);
_tempDataServiceMock.Verify(s => s.Peek<FlashMessage>(messageType), Times.Once);
}

[Fact]
public void SetSuccessMessage_ShouldFormatHeadingAndDescriptionWithParameters()
{
var formatString = "Visit <a href=\"/organisation/{organisationId}\">{organisationName}</a>";
var urlParameters = new Dictionary<string, string> { { "organisationId", "12345" } };
var htmlParameters = new Dictionary<string, string> { { "organisationName", "Test Organisation" } };

var expectedOutput = "Visit <a href=\"/organisation/12345\">Test Organisation</a>";

_flashMessageService.SetSuccessMessage(formatString, formatString, null, urlParameters, htmlParameters);

_tempDataServiceMock.Verify(s =>
s.Put(FlashMessageTypes.Success,
It.Is<FlashMessage>(m => m.Heading == expectedOutput && m.Description == expectedOutput)),
Times.Once);
}

[Fact]
public void SetImportantMessage_ShouldHandleNullDescriptionAndFormatHeading()
{
var heading = "Access <a href=\"/organisation/{organisationId}\">your account</a>.";
var urlParameters = new Dictionary<string, string> { { "organisationId", "67890" } };

var expectedHeading = "Access <a href=\"/organisation/67890\">your account</a>.";

_flashMessageService.SetImportantMessage(heading, null, null,urlParameters, null);

_tempDataServiceMock.Verify(s =>
s.Put(FlashMessageTypes.Important,
It.Is<FlashMessage>(m => m.Heading == expectedHeading && m.Description == null)),
Times.Once);
}

[Fact]
public void SetImportantMessage_ShouldIgnoreUnusedPlaceholders()
{
var heading = "Hello {userName}";
var urlParameters = new Dictionary<string, string> { { "irrelevantParam", "12345" } };
var htmlParameters = new Dictionary<string, string> { { "userName", "Alice" } };

var expectedHeading = "Hello Alice";

_flashMessageService.SetImportantMessage(heading, null, null, urlParameters, htmlParameters);

_tempDataServiceMock.Verify(s =>
s.Put(FlashMessageTypes.Important,
It.Is<FlashMessage>(m => m.Heading == expectedHeading && m.Description == null)),
Times.Once);
}

[Fact]
public void SetSuccessMessage_ShouldFormatNotBeVulnerabletoXSS()
{
var formatString = "Visit <a href=\"/organisation/{organisationId}\">{organisationName}</a>";
var urlParameters = new Dictionary<string, string> { { "organisationId", "<script>alert(\"hello\")</script>" } };
var htmlParameters = new Dictionary<string, string> { { "organisationName", "<script>alert(\"hello\")</script>" } };

var expectedOutput = "Visit <a href=\"/organisation/%3Cscript%3Ealert(%22hello%22)%3C%2Fscript%3E\">&lt;script&gt;alert(&quot;hello&quot;)&lt;/script&gt;</a>";

_flashMessageService.SetSuccessMessage(formatString, formatString, formatString, urlParameters, htmlParameters);

_tempDataServiceMock.Verify(s =>
s.Put(FlashMessageTypes.Success,
It.Is<FlashMessage>(m => m.Heading == expectedOutput && m.Description == expectedOutput && m.Title == expectedOutput)),
Times.Once);
}
}
14 changes: 11 additions & 3 deletions Frontend/CO.CDP.OrganisationApp.Tests/Pages/ContactUsModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ namespace CO.CDP.OrganisationApp.Tests.Pages;
public class ContactUsModelTests
{
private readonly Mock<IOrganisationClient> _mockOrganisationClient = new();
private readonly Mock<ITempDataService> _mockTempDataServiceClient = new();
private readonly Mock<IFlashMessageService> _mockFlashMessageService = new();
private readonly ContactUsModel _pageModel;

public ContactUsModelTests()
{
_pageModel = new ContactUsModel(_mockOrganisationClient.Object, _mockTempDataServiceClient.Object)
_pageModel = new ContactUsModel(_mockOrganisationClient.Object, _mockFlashMessageService.Object)
{
EmailAddress = "[email protected]",
Message = "message",
Expand Down Expand Up @@ -55,6 +55,14 @@ public async Task OnPost_FailedToSend_CallsTempDataService()
var result = await _pageModel.OnPost();

result.Should().BeOfType<PageResult>();
_mockTempDataServiceClient.Verify(client => client.Put(It.IsAny<string>(), It.IsAny<FlashMessage>()), Times.Once);

_mockFlashMessageService.Verify(api => api.SetFailureMessage(
"There is a problem with the service",
"Your message could not be sent. Try again or come back later.",
"Failed to send",
null,
null
),
Times.Once);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System.Net;

namespace CO.CDP.OrganisationApp.Tests.Pages.Organisation;
public class OrganisationRegisterBuyerAsSupplierTests
{
private readonly Mock<IOrganisationClient> organisationClientMock;
private readonly Mock<ITempDataService> tempDataService;
private readonly Mock<IFlashMessageService> flashMessageServiceMock;
private readonly OrganisationRegisterBuyerAsSupplierModel _model;
private readonly Guid orgGuid = new("8b1e9502-2dd5-49fa-ad56-26bae0f85b85");

public OrganisationRegisterBuyerAsSupplierTests()
{
organisationClientMock = new Mock<IOrganisationClient>();
tempDataService = new Mock<ITempDataService>();
_model = new OrganisationRegisterBuyerAsSupplierModel(organisationClientMock.Object, tempDataService.Object);
flashMessageServiceMock = new Mock<IFlashMessageService>();
_model = new OrganisationRegisterBuyerAsSupplierModel(organisationClientMock.Object, flashMessageServiceMock.Object);
}

[Fact]
Expand All @@ -36,7 +37,14 @@ public async Task ShouldUpdateOrganisation_WhenOrganisationIsNotYetATenderer()
uo.Organisation.Roles.SequenceEqual(new[] { PartyRole.Tenderer }))),
Times.Once);

tempDataService.Verify(td => td.Put(FlashMessageTypes.Success, It.Is<FlashMessage>(fm => fm.Heading == "You have been registered as a supplier")));
flashMessageServiceMock.Verify(api => api.SetSuccessMessage(
"You have been registered as a supplier",
It.IsAny<string>(),
null,
null,
null
),
Times.Once);

var redirectResult = result.Should().BeOfType<RedirectToPageResult>().Subject;
redirectResult.PageName.Should().Be("OrganisationOverview");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CO.CDP.Localization;
using CO.CDP.Organisation.WebApiClient;
using CO.CDP.OrganisationApp.Constants;
using CO.CDP.OrganisationApp.Models;
Expand All @@ -8,12 +9,13 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Moq;
using System.Net;

namespace CO.CDP.OrganisationApp.Tests.Pages.Registration;
public class CompanyHouseNumberQuestionTests
{
private readonly Mock<ISession> sessionMock;
private readonly Mock<ITempDataService> tempDataServiceMock;
private readonly Mock<IFlashMessageService> flashMessageServiceMock;
private readonly Mock<ICompaniesHouseApi> companiesHouseApiMock;
private readonly Mock<IOrganisationClient> organisationClientMock;

Expand All @@ -22,7 +24,7 @@ public CompanyHouseNumberQuestionTests()
sessionMock = new Mock<ISession>();
sessionMock.Setup(session => session.Get<UserDetails>(Session.UserDetailsKey))
.Returns(new UserDetails { UserUrn = "urn:test" });
tempDataServiceMock = new Mock<ITempDataService>();
flashMessageServiceMock = new Mock<IFlashMessageService>();
companiesHouseApiMock = new Mock<ICompaniesHouseApi>();
organisationClientMock = new Mock<IOrganisationClient>();
}
Expand Down Expand Up @@ -116,16 +118,32 @@ public async Task OnPost_WhenModelStateIsValidAndOrganisationAlreadyExists_Shoul

GivenRegistrationIsInProgress(model.HasCompaniesHouseNumber, model.CompaniesHouseNumber);

organisationClientMock.Setup(o => o.LookupOrganisationAsync(string.Empty, It.IsAny<string>()))
.ReturnsAsync(GivenOrganisation());

var result = await model.OnPost();

tempDataServiceMock.Verify(api => api.Put(
FlashMessageTypes.Important,
It.Is<FlashMessage>(f => f.Heading == model.NotificationBannerCompanyAlreadyRegistered.Heading)),
Times.Once);
Dictionary<string, string> urlParameters = new() { ["organisationIdentifier"] = model.OrganisationIdentifier };
Dictionary<string, string> htmlParameters = new() { ["organisationName"] = model.OrganisationName };

flashMessageServiceMock.Verify(api => api.SetImportantMessage(
StaticTextResource.OrganisationRegistration_CompanyHouseNumberQuestion_CompanyAlreadyRegistered_NotificationBanner,
null,
null,
It.Is<Dictionary<string, string>>(d => d["organisationIdentifier"] == model.OrganisationIdentifier),
It.Is<Dictionary<string, string>>(d => d["organisationName"] == model.OrganisationName)
),
Times.Once);


result.Should().BeOfType<PageResult>();
}

private static CDP.Organisation.WebApiClient.Organisation GivenOrganisation()
{
return new CO.CDP.Organisation.WebApiClient.Organisation(null, null, null, null, new Guid(), null, "Test Org", []);
}

[Fact]
public async Task OnPost_WhenModelStateIsValidAndCompanyNotFoundAtComapniesHouse_ShouldShowNotificationBanner()
{
Expand All @@ -142,10 +160,15 @@ public async Task OnPost_WhenModelStateIsValidAndCompanyNotFoundAtComapniesHouse

var result = await model.OnPost();

tempDataServiceMock.Verify(api => api.Put(
FlashMessageTypes.Important,
It.Is<FlashMessage>(f => f.Heading == model.NotificationBannerCompanyNotFound.Heading)),
Times.Once);
flashMessageServiceMock.Verify(api => api.SetImportantMessage(
StaticTextResource.OrganisationRegistration_CompanyHouseNumberQuestion_CompanyNotFound_NotificationBanner,
null,
null,
null,
null
),
Times.Once);

result.Should().BeOfType<PageResult>();
}

Expand All @@ -166,7 +189,7 @@ private CompanyHouseNumberQuestionModel GivenCompaniesHouseQuestionModel()
return new CompanyHouseNumberQuestionModel(sessionMock.Object,
companiesHouseApiMock.Object,
organisationClientMock.Object,
tempDataServiceMock.Object);
flashMessageServiceMock.Object);
}
private static RegistrationDetails DummyRegistrationDetails(bool? hasNumber, string companyHouseNumber)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class JoinOrganisationModelTests
{
private readonly Mock<IOrganisationClient> _organisationClientMock;
private readonly Mock<ISession> _sessionMock;
private readonly Mock<ITempDataService> _tempDataMock;
private readonly Mock<IFlashMessageService> flashMessageServiceMock;
private readonly JoinOrganisationModel _joinOrganisationModel;
private readonly Guid _organisationId = Guid.NewGuid();
private readonly string _identifier = "GB-COH:123456789";
Expand All @@ -25,10 +25,10 @@ public JoinOrganisationModelTests()
{
_organisationClientMock = new Mock<IOrganisationClient>();
_sessionMock = new Mock<ISession>();
_tempDataMock = new Mock<ITempDataService>();
flashMessageServiceMock = new Mock<IFlashMessageService>();
_sessionMock.Setup(s => s.Get<UserDetails>(Session.UserDetailsKey))
.Returns(new UserDetails() { UserUrn = "testUserUrn", PersonId = _personId});
_joinOrganisationModel = new JoinOrganisationModel(_organisationClientMock.Object, _sessionMock.Object, _tempDataMock.Object);
_joinOrganisationModel = new JoinOrganisationModel(_organisationClientMock.Object, _sessionMock.Object, flashMessageServiceMock.Object);
_organisation = new CO.CDP.Organisation.WebApiClient.Organisation(null, null, null, null, _organisationId, null, "Test Org", []);
}

Expand Down Expand Up @@ -140,10 +140,14 @@ public async Task OnPost_CreateJoinRequestThrowsApiException_SetsFlashMessageAnd

var result = await _joinOrganisationModel.OnPost(_identifier);

_tempDataMock.Verify(tempData => tempData.Put(
FlashMessageTypes.Important,
It.IsAny<FlashMessage>()),
Times.Once);
flashMessageServiceMock.Verify(api => api.SetImportantMessage(
ErrorMessagesList.AlreadyMemberOfOrganisation,
null,
null,
null,
null
),
Times.Once);

result.Should().BeOfType<PageResult>();
}
Expand Down
Loading
Loading