Skip to content

Commit

Permalink
Merge pull request #781 from cabinetoffice/feature/DP-689-request-to-…
Browse files Browse the repository at this point in the history
…join-notify

DP-689: Registration - Request to Join Organisation- Notify
  • Loading branch information
rmohammed-goaco authored Oct 22, 2024
2 parents edcd444 + 8ef55b3 commit ef3797e
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
using CO.CDP.Organisation.WebApi.Model;
using CO.CDP.OrganisationInformation.Persistence;
using AutoMapper;
using CO.CDP.GovUKNotify;
using CO.CDP.GovUKNotify.Models;
using CO.CDP.Organisation.WebApi.UseCase;
using CO.CDP.OrganisationInformation;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using OrganisationJoinRequest = CO.CDP.Organisation.WebApi.Model.OrganisationJoinRequest;
using Person = CO.CDP.OrganisationInformation.Persistence.Person;

Expand All @@ -17,8 +20,8 @@ public class CreateOrganisationJoinRequestUseCaseTests
private readonly Mock<IPersonRepository> _mockPersonRepository;
private readonly Mock<IOrganisationJoinRequestRepository> _mockOrganisationJoinRequestRepository;
private readonly Mock<IMapper> _mockMapper;
private readonly Mock<IConfiguration> _mockConfiguration;
private readonly Func<Guid> _guidFactory;
private readonly Mock<IGovUKNotifyApiClient> _notifyApiClient = new();
private readonly Mock<ILogger<CreateOrganisationJoinRequestUseCase>> _logger = new();
private readonly CreateOrganisationJoinRequestUseCase _useCase;
private readonly OrganisationInformation.Persistence.Organisation _organisation;
private readonly Person _person;
Expand All @@ -29,8 +32,6 @@ public CreateOrganisationJoinRequestUseCaseTests()
_mockPersonRepository = new Mock<IPersonRepository>();
_mockOrganisationJoinRequestRepository = new Mock<IOrganisationJoinRequestRepository>();
_mockMapper = new Mock<IMapper>();
_mockConfiguration = new Mock<IConfiguration>();
_guidFactory = () => Guid.NewGuid();
_organisation = new OrganisationInformation.Persistence.Organisation
{
Id = 1,
Expand All @@ -45,15 +46,28 @@ public CreateOrganisationJoinRequestUseCaseTests()
Guid = default,
FirstName = "John",
LastName = "Johnson",
Email = "john@johson.com"
Email = "john@johnson.com"
};

var inMemorySettings = new List<KeyValuePair<string, string?>>
{
new("OrganisationAppUrl", "http://baseurl/"),
new("GOVUKNotify:RequestToJoinNotifyOrgAdminTemplateId", "RequestToJoinNotifyOrgAdminTemplateId"),
new("GOVUKNotify:RequestToJoinConfirmationEmailTemplateId", "RequestToJoinConfirmationEmailTemplateId")
};

IConfiguration mockConfiguration = new ConfigurationBuilder()
.AddInMemoryCollection(inMemorySettings)
.Build();

_useCase = new CreateOrganisationJoinRequestUseCase(
_mockOrganisationRepository.Object,
_mockPersonRepository.Object,
_mockOrganisationJoinRequestRepository.Object,
_guidFactory,
_mockMapper.Object
_mockMapper.Object,
mockConfiguration,
_notifyApiClient.Object,
_logger.Object
);
}

Expand Down Expand Up @@ -120,4 +134,102 @@ public async Task Execute_ShouldCreateAndSaveOrganisationJoinRequest()
_mockOrganisationJoinRequestRepository.Verify(repo => repo.Save(It.IsAny<CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest>()), Times.Once);
_mockMapper.Verify(mapper => mapper.Map<OrganisationJoinRequest>(It.IsAny<CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest>()), Times.Once);
}

[Fact]
public async Task ItShouldSendUserRequestSentEmail()
{
var createJoinRequestCommand = new CreateOrganisationJoinRequest { PersonId = _person.Guid };

_mockOrganisationRepository.Setup(repo => repo.Find(_organisation.Guid))
.ReturnsAsync(_organisation);

_mockPersonRepository.Setup(repo => repo.Find(_person.Guid))
.ReturnsAsync(_person);

_mockMapper.Setup(mapper => mapper.Map<OrganisationJoinRequest>(It.IsAny<CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest>()))
.Returns(new OrganisationJoinRequest
{
Status = OrganisationJoinRequestStatus.Pending,
Id = default,
Person = null!,
Organisation = null!,
ReviewedBy = null!,
ReviewedOn = default
});

await _useCase.Execute((_organisation.Guid, createJoinRequestCommand));

_notifyApiClient.Verify(x => x.SendEmail(It.Is<EmailNotificationRequest>(req =>
req.EmailAddress == _person.Email &&
req.TemplateId == "RequestToJoinConfirmationEmailTemplateId" &&
req.Personalisation!["org_name"] == _organisation.Name
)), Times.Once);
}

[Fact]
public async Task ItShouldSendEmailsToOrgAdmins()
{
var createJoinRequestCommand = new CreateOrganisationJoinRequest { PersonId = _person.Guid };

_mockOrganisationRepository.Setup(repo => repo.Find(_organisation.Guid))
.ReturnsAsync(_organisation);

_mockPersonRepository.Setup(repo => repo.Find(_person.Guid))
.ReturnsAsync(_person);

var adminUsers = new List<Person>
{
new Person
{
Email = "admin1@example.com",
FirstName = "Admin",
LastName = "One",
Guid = new Guid()
},
new Person
{
Email = "admin2@example.com",
FirstName = "Admin",
LastName = "Two",
Guid = new Guid()
}
};

_mockOrganisationRepository.Setup(repo => repo.FindOrganisationPersons(_organisation.Guid))
.ReturnsAsync(adminUsers.Select(admin => new OrganisationPerson
{
Person = admin,
Scopes = ["ADMIN"],
Organisation = null!
}).ToList());

_mockMapper.Setup(mapper => mapper.Map<OrganisationJoinRequest>(It.IsAny<CO.CDP.OrganisationInformation.Persistence.OrganisationJoinRequest>()))
.Returns(new OrganisationJoinRequest
{
Status = OrganisationJoinRequestStatus.Pending,
Id = default,
Person = null!,
Organisation = null!,
ReviewedBy = null!,
ReviewedOn = default
});

await _useCase.Execute((_organisation.Guid, createJoinRequestCommand));

_notifyApiClient.Verify(x => x.SendEmail(It.Is<EmailNotificationRequest>(req =>
req.EmailAddress == "admin1@example.com" &&
req.TemplateId == "RequestToJoinNotifyOrgAdminTemplateId" &&
req.Personalisation!["org_name"] == _organisation.Name &&
req.Personalisation["first_name"] == "Admin" &&
req.Personalisation["last_name"] == "One"
)), Times.Once);

_notifyApiClient.Verify(x => x.SendEmail(It.Is<EmailNotificationRequest>(req =>
req.EmailAddress == "admin2@example.com" &&
req.TemplateId == "RequestToJoinNotifyOrgAdminTemplateId" &&
req.Personalisation!["org_name"] == _organisation.Name &&
req.Personalisation["first_name"] == "Admin" &&
req.Personalisation["last_name"] == "Two"
)), Times.Once);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using OrganisationJoinRequest = CO.CDP.Organisation.WebApi.Model.OrganisationJoinRequest;
using Person = CO.CDP.OrganisationInformation.Persistence.Person;
using AutoMapper;
using CO.CDP.Authentication;
using CO.CDP.GovUKNotify;
using CO.CDP.GovUKNotify.Models;

namespace CO.CDP.Organisation.WebApi.UseCase;

Expand All @@ -12,15 +15,21 @@ public class CreateOrganisationJoinRequestUseCase(
Persistence.IPersonRepository personRepository,
Persistence.IOrganisationJoinRequestRepository organisationJoinRequestRepository,
Func<Guid> guidFactory,
IMapper mapper)
IMapper mapper,
IConfiguration configuration,
IGovUKNotifyApiClient govUKNotifyApiClient,
ILogger<CreateOrganisationJoinRequestUseCase> logger)
: IUseCase<(Guid organisationId, CreateOrganisationJoinRequest createOrganisationJoinRequestCommand), OrganisationJoinRequest>
{
public CreateOrganisationJoinRequestUseCase(
Persistence.IOrganisationRepository organisationRepository,
Persistence.IPersonRepository personRepository,
Persistence.IOrganisationJoinRequestRepository organisationJoinRequestRepository,
IMapper mapper
) : this(organisationRepository, personRepository, organisationJoinRequestRepository, Guid.NewGuid, mapper)
IMapper mapper,
IConfiguration configuration,
IGovUKNotifyApiClient govUKNotifyApiClient,
ILogger<CreateOrganisationJoinRequestUseCase> logger
) : this(organisationRepository, personRepository, organisationJoinRequestRepository, Guid.NewGuid, mapper, configuration, govUKNotifyApiClient, logger)
{

}
Expand All @@ -37,6 +46,9 @@ public async Task<OrganisationJoinRequest> Execute((Guid organisationId, CreateO

organisationJoinRequestRepository.Save(organisationJoinRequest);

await NotifyUserRequestSent(organisation: organisation, person: person);
await NotifyOrgAdminsOfApprovalRequest(organisation: organisation);

return mapper.Map<OrganisationJoinRequest>(organisationJoinRequest);
}

Expand All @@ -55,4 +67,92 @@ Person person

return organisationJoinRequest;
}

private async Task NotifyOrgAdminsOfApprovalRequest(Persistence.Organisation organisation)
{
var baseAppUrl = configuration.GetValue<string>("OrganisationAppUrl") ?? "";
var templateId = configuration.GetValue<string>("GOVUKNotify:RequestToJoinNotifyOrgAdminTemplateId") ?? "";

var missingConfigs = new List<string>();

if (string.IsNullOrEmpty(baseAppUrl)) missingConfigs.Add("OrganisationAppUrl");
if (string.IsNullOrEmpty(templateId)) missingConfigs.Add("GOVUKNotify:RequestToJoinNotifyOrgAdminTemplateId");

if (missingConfigs.Count != 0)
{
logger.LogError(new Exception("Unable to send email to organisation admins"), $"Missing configuration keys: {string.Join(", ", missingConfigs)}. Unable to send email to organisation admins.");
return;
}

var requestLink = new Uri(new Uri(baseAppUrl), $"/organisation/{organisation.Guid}/users/user-summary").ToString();

var organisationAdminUsers = await GetOrganisationAdminUsers(organisation);

foreach (var p in organisationAdminUsers)
{
var emailRequest = new EmailNotificationRequest
{
EmailAddress = p.Email,
TemplateId = templateId,
Personalisation = new Dictionary<string, string>
{
{ "org_name", organisation.Name },
{ "request_link", requestLink },
{ "first_name", p.FirstName },
{ "last_name", p.LastName }
}
};

try
{
await govUKNotifyApiClient.SendEmail(emailRequest);
}
catch
{
return;
}
}
}

private async Task NotifyUserRequestSent(Persistence.Organisation organisation, Person person)
{
var templateId = configuration.GetValue<string>("GOVUKNotify:RequestToJoinConfirmationEmailTemplateId") ?? "";

var missingConfigs = new List<string>();

if (string.IsNullOrEmpty(templateId)) missingConfigs.Add("GOVUKNotify:RequestToJoinConfirmationEmailTemplateId");

if (missingConfigs.Count != 0)
{
logger.LogError(new Exception("Unable to send email to buyer user"), $"Missing configuration keys: {string.Join(", ", missingConfigs)}. Unable to send email to buyer user.");
return;
}

var emailRequest = new EmailNotificationRequest
{
EmailAddress = person.Email,
TemplateId = templateId,
Personalisation = new Dictionary<string, string>
{
{ "org_name", organisation.Name },
{ "first_name", person.FirstName },
{ "last_name", person.LastName }
}
};

try
{
await govUKNotifyApiClient.SendEmail(emailRequest);
}
catch
{
return;
}
}

private async Task<List<Person>> GetOrganisationAdminUsers(Persistence.Organisation organisation)
{
var organisationPersons = await organisationRepository.FindOrganisationPersons(organisation.Guid);
return organisationPersons.Where(op => op.Scopes.Contains(Constants.OrganisationPersonScope.Admin)).Select(op => op.Person).ToList();
}
}
2 changes: 2 additions & 0 deletions Services/CO.CDP.Organisation.WebApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"PersonInviteEmailTemplateId": "94beca4e-2ccc-4b57-8cab-6940305596db",
"RequestReviewApplicationEmailTemplateId": "5b383432-5382-4a85-a19a-20e341332cf2",
"BuyerApprovedEmailTemplateId": "ebb6702c-5c08-4ed1-a611-18311f5072ee",
"RequestToJoinNotifyOrgAdminTemplateId": "31cde325-d6a3-46b5-9d82-d2c5c43b83b1",
"RequestToJoinConfirmationEmailTemplateId": "9c146076-f13e-4e4c-855d-d3abf25e0b87",
"SupportAdminEmailAddress": ""
},
"ForwardedHeaders": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public void Dispose()
public async Task<IEnumerable<OrganisationPerson>> FindOrganisationPersons(Guid organisationId)
{
return await context.Set<OrganisationPerson>()
.Include(p => p.Person)
.Where(o => o.Organisation.Guid == organisationId)
.Include(op => op.Person)
.Where(op => op.Organisation.Guid == organisationId)
.ToListAsync();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ public interface IOrganisationRepository : IDisposable
public void SaveOrganisationPerson(OrganisationPerson organisationPerson);

public Task<Organisation?> Find(Guid organisationId);

public Task<IEnumerable<OrganisationPerson>> FindOrganisationPersons(Guid organisationId);

public Task<OrganisationPerson?> FindOrganisationPerson(Guid organisationId, Guid personId);

public Task<OrganisationPerson?> FindOrganisationPerson(Guid organisationId, string userUrn);
Expand Down

0 comments on commit ef3797e

Please sign in to comment.