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-689: Registration - Request to Join Organisation- Notify #781

Merged
merged 6 commits into from
Oct 22, 2024
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
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