Skip to content

Commit

Permalink
Merge pull request #880 from cabinetoffice/feature/dp-786-org-name-or…
Browse files Browse the repository at this point in the history
…-email-change-reapproval-notification

DP-786 - Rejected/pending approval flow, and resending support admin approval emails
  • Loading branch information
andymantell authored Nov 14, 2024
2 parents 4aec618 + 321a350 commit aa2b691
Show file tree
Hide file tree
Showing 21 changed files with 855 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using CO.CDP.Organisation.WebApiClient;
using CO.CDP.OrganisationApp.Pages;
using CO.CDP.OrganisationApp.Pages.Organisation;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
Expand Down Expand Up @@ -28,6 +28,24 @@ public async Task OnGet_WithValidId_CallsGetOrganisationAsync()
await _model.OnGet();

_organisationClientMock.Verify(c => c.GetOrganisationAsync(id), Times.Once);
_organisationClientMock.Verify(c => c.GetOrganisationReviewsAsync(id), Times.Never);
}

[Fact]
public async Task OnGet_WithPendingBuyers_CallsGetOrganisationReviewsAsync()
{
var id = Guid.NewGuid();
_model.Id = id;
_organisationClientMock.Setup(o => o.GetOrganisationAsync(id))
.ReturnsAsync(GivenOrganisationClientModel(id: id, pendingRoles: [PartyRole.Buyer]));

_organisationClientMock.Setup(o => o.GetOrganisationReviewsAsync(id))
.ReturnsAsync([new Review(null, null, null, ReviewStatus.Pending)]);

await _model.OnGet();

_organisationClientMock.Verify(c => c.GetOrganisationAsync(id), Times.Once);
_organisationClientMock.Verify(c => c.GetOrganisationReviewsAsync(id), Times.Once);
}

[Fact]
Expand All @@ -43,8 +61,19 @@ public async Task OnGet_PageNotFound()
.Which.Url.Should().Be("/page-not-found");
}

private static CO.CDP.Organisation.WebApiClient.Organisation GivenOrganisationClientModel(Guid? id)
private static CO.CDP.Organisation.WebApiClient.Organisation GivenOrganisationClientModel(Guid? id, ICollection<PartyRole>? pendingRoles = null)
{
return new CO.CDP.Organisation.WebApiClient.Organisation(additionalIdentifiers: null, addresses: null, contactPoint: null, id: id ?? Guid.NewGuid(), identifier: null, name: "Test Org", roles: [], details: new Details(approval: null, pendingRoles: []));
return new CO.CDP.Organisation.WebApiClient.Organisation(
additionalIdentifiers: null,
addresses: null,
contactPoint: null,
id: id ?? Guid.NewGuid(),
identifier: null,
name: "Test Org",
roles: [],
details: new Details(
approval: null,
pendingRoles: pendingRoles != null ? pendingRoles : []
));
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CO.CDP.Organisation.WebApiClient;
using CO.CDP.OrganisationApp.Pages.Organisation;
using CO.CDP.Tenant.WebApiClient;
using FluentAssertions;
Expand All @@ -9,14 +10,16 @@ namespace CO.CDP.OrganisationApp.Tests.Pages;
public class OrganisationSelectionTest
{
private readonly Mock<ISession> sessionMock;
private readonly Mock<ITenantClient> organisationClientMock;
private readonly Mock<ITenantClient> tenantClientMock;
private readonly Mock<IOrganisationClient> organisationClientMock;

public OrganisationSelectionTest()
{
sessionMock = new Mock<ISession>();
sessionMock.Setup(session => session.Get<Models.UserDetails>(Session.UserDetailsKey))
.Returns(new Models.UserDetails { UserUrn = "urn:test" });
organisationClientMock = new Mock<ITenantClient>();
tenantClientMock = new Mock<ITenantClient>();
organisationClientMock = new Mock<IOrganisationClient>();
}

[Fact]
Expand All @@ -27,20 +30,69 @@ public async Task OnGet_WhenSessionIsValid_ShouldPopulatePageModel()
sessionMock.Setup(s => s.Get<Models.UserDetails>(Session.UserDetailsKey))
.Returns(new Models.UserDetails { UserUrn = "urn:test" });

organisationClientMock.Setup(o => o.LookupTenantAsync())
tenantClientMock.Setup(o => o.LookupTenantAsync())
.ReturnsAsync(GetUserTenant());

var actionResult = await model.OnGet();

model.UserOrganisations.Should().HaveCount(1);
}

[Fact]
public async Task OnGet_WhenOrgHasPendingRoles_ShouldCallGetOrganisationReviewsAsync()
{
var model = GivenOrganisationSelectionModelModel();

sessionMock.Setup(s => s.Get<Models.UserDetails>(Session.UserDetailsKey))
.Returns(new Models.UserDetails { UserUrn = "urn:test" });

tenantClientMock.Setup(o => o.LookupTenantAsync())
.ReturnsAsync(GetUserTenant(pendingRoles: [PartyRole.Buyer]));

organisationClientMock.Setup(o => o.GetOrganisationReviewsAsync(It.IsAny<Guid>()))
.ReturnsAsync([GivenReview(ReviewStatus.Pending)]);

var actionResult = await model.OnGet();

organisationClientMock.Verify(c => c.GetOrganisationReviewsAsync(It.IsAny<Guid>()), Times.Once);

model.UserOrganisations.Should().HaveCount(1);
model.UserOrganisations?.FirstOrDefault().Review?.Status.Should().Be(ReviewStatus.Pending);
}

[Fact]
public async Task OnGet_WhenOrgHasPendingRoles_AndIsRejected_CallToGetOrganisationReviewsAsync_ShouldReturnRejectedStatus()
{
var model = GivenOrganisationSelectionModelModel();

sessionMock.Setup(s => s.Get<Models.UserDetails>(Session.UserDetailsKey))
.Returns(new Models.UserDetails { UserUrn = "urn:test" });

tenantClientMock.Setup(o => o.LookupTenantAsync())
.ReturnsAsync(GetUserTenant(pendingRoles: [PartyRole.Buyer]));

organisationClientMock.Setup(o => o.GetOrganisationReviewsAsync(It.IsAny<Guid>()))
.ReturnsAsync([GivenReview(ReviewStatus.Rejected)]);

var actionResult = await model.OnGet();

organisationClientMock.Verify(c => c.GetOrganisationReviewsAsync(It.IsAny<Guid>()), Times.Once);

model.UserOrganisations.Should().HaveCount(1);
model.UserOrganisations?.FirstOrDefault().Review?.Status.Should().Be(ReviewStatus.Rejected);
}

private OrganisationSelectionModel GivenOrganisationSelectionModelModel()
{
return new OrganisationSelectionModel(organisationClientMock.Object, sessionMock.Object);
return new OrganisationSelectionModel(tenantClientMock.Object, organisationClientMock.Object, sessionMock.Object);
}

private static Review GivenReview(ReviewStatus reviewStatus)
{
return new Review(null, null, null, reviewStatus);
}

private TenantLookup GetUserTenant()
private TenantLookup GetUserTenant(ICollection<PartyRole>? pendingRoles = null)
{
return new TenantLookup(
new List<UserTenant>
Expand All @@ -54,7 +106,7 @@ private TenantLookup GetUserTenant()
id: Guid.NewGuid(),
name: "Acme Ltd",
roles: new List<PartyRole> { PartyRole.Payee },
pendingRoles: [],
pendingRoles: pendingRoles != null ? pendingRoles : [],
scopes: new List<string> { "Scope" },
uri: new Uri("http://www.acme.com"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,38 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">

@if (organisationDetails.IsPendingBuyer())
{
<div class="govuk-notification-banner" role="region" aria-labelledby="govuk-notification-banner-title" data-module="govuk-notification-banner">
<div class="govuk-notification-banner__header">
<h2 class="govuk-notification-banner__title" id="govuk-notification-banner-title">
Important
</h2>
@if (Model.Review != null) {
if (Model.Review.Status == CDP.Organisation.WebApiClient.ReviewStatus.Rejected)
{
<div class="govuk-notification-banner" role="region" aria-labelledby="govuk-notification-banner-title" data-module="govuk-notification-banner">
<div class="govuk-notification-banner__header">
<h2 class="govuk-notification-banner__title" id="govuk-notification-banner-title">
Registration not approved
</h2>
</div>
<div class="govuk-notification-banner__content">
<p class="govuk-notification-banner__heading">
We could not approve your request because:
</p>
<p class="govuk-body">@Model.Review.Comment</p>
</div>
</div>
<div class="govuk-notification-banner__content">
<p class="govuk-notification-banner__heading">
Buyer registration pending approval.
</p>
}
else if (Model.Review.Status == CDP.Organisation.WebApiClient.ReviewStatus.Pending)
{
<div class="govuk-notification-banner" role="region" aria-labelledby="govuk-notification-banner-title" data-module="govuk-notification-banner">
<div class="govuk-notification-banner__header">
<h2 class="govuk-notification-banner__title" id="govuk-notification-banner-title">
Important
</h2>
</div>
<div class="govuk-notification-banner__content">
<p class="govuk-notification-banner__heading">
Buyer registration pending approval.
</p>
</div>
</div>
</div>
}
}

<fieldset class="govuk-fieldset">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using OrganisationWebApiClient = CO.CDP.Organisation.WebApiClient;

namespace CO.CDP.OrganisationApp.Pages;
namespace CO.CDP.OrganisationApp.Pages.Organisation;

[Authorize(Policy = OrgScopeRequirement.Viewer)]
public class OrganisationOverviewModel(IOrganisationClient organisationClient) : PageModel
{
public OrganisationWebApiClient.Organisation? OrganisationDetails { get; set; }
public Review? Review { get; set; }

[BindProperty(SupportsGet = true)]
public Guid Id { get; set; }
Expand All @@ -20,6 +21,10 @@ public async Task<IActionResult> OnGet()
try
{
OrganisationDetails = await organisationClient.GetOrganisationAsync(Id);
if (OrganisationDetails.Details.PendingRoles.Count > 0)
{
Review = (await organisationClient.GetOrganisationReviewsAsync(Id)).FirstOrDefault();
}
return Page();
}
catch (ApiException ex) when (ex.StatusCode == 404)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,26 @@
</thead>
<tbody class="govuk-table__body">
@{
foreach (var org in Model.UserOrganisations)
foreach (var (org, review) in Model.UserOrganisations)
{
<tr class="govuk-table__row">
<th scope="row" class="govuk-table__header">
@org.Name
@{
if (org.IsPendingBuyer())
if (review != null)
{
<strong class="govuk-tag govuk-tag--blue">
Pending approval
</strong>
if (review.Status == ReviewStatus.Rejected)
{
<strong class="govuk-tag govuk-tag--red">
Not approved
</strong>
}
else if (review.Status == ReviewStatus.Pending)
{
<strong class="govuk-tag govuk-tag--blue">
Pending approval
</strong>
}
}
}
</th>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
using CO.CDP.Tenant.WebApiClient;
using Microsoft.AspNetCore.Mvc;
using CO.CDP.Organisation.WebApiClient;

namespace CO.CDP.OrganisationApp.Pages.Organisation;

public class OrganisationSelectionModel(
ITenantClient tenantClient,
IOrganisationClient organisationClient,
ISession session) : LoggedInUserAwareModel(session)
{
public IList<UserOrganisation> UserOrganisations { get; set; } = [];
public IList<(UserOrganisation Organisation, Review? Review)> UserOrganisations { get; set; } = [];

public string? Error { get; set; }

public async Task<IActionResult> OnGet()
{
var usersTenant = await tenantClient.LookupTenantAsync();

usersTenant.Tenants.ToList()
.ForEach(t => t.Organisations.ToList()
.ForEach(o => UserOrganisations.Add(o)));
UserOrganisations = await Task.WhenAll(usersTenant.Tenants
.SelectMany(t => t.Organisations)
.Select(async organisation =>
{
Review? review = null;
if (organisation.PendingRoles.Count > 0)
{
var reviews = await organisationClient.GetOrganisationReviewsAsync(organisation.Id);
review = reviews?.FirstOrDefault();
}

return (organisation, review);
}));

return Page();
}
}
}
4 changes: 2 additions & 2 deletions Frontend/CO.CDP.OrganisationApp/Pages/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<a href="#main-content" class="govuk-skip-link" data-module="govuk-skip-link">Skip to main content</a>

<header class="govuk-header" role="banner" data-module="govuk-header">
<div class="govuk-header__container govuk-width-container">
<div class="govuk-header__container govuk-width-container @ViewData["ContainerClasses"]">
<div class="govuk-header__logo">
<a href="https://gov.uk" class="govuk-header__link govuk-header__link--homepage">
<svg focusable="false"
Expand Down Expand Up @@ -131,7 +131,7 @@
</div>
</div>
</header>
<div class="govuk-width-container app-width-container">
<div class="govuk-width-container @ViewData["ContainerClasses"]">
<div class="govuk-phase-banner">
<p class="govuk-phase-banner__content">
<strong class="govuk-tag govuk-phase-banner__content__tag">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

@{
ViewData["Title"] = Model.Title;
ViewData["ContainerClasses"] = "app-width-container--wide";
}

<main class="govuk-main-wrapper" id="main-content">
Expand Down
11 changes: 10 additions & 1 deletion Frontend/CO.CDP.OrganisationApp/wwwroot/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,13 @@
.app-language-select__list-item [aria-current] {
color: #b1b4b6;
font-weight: 700;
}
}

/**
* Wide container, reserved for support admin use only
*/
.app-width-container--wide {
max-width: none;
margin-left: 30px;
margin-right: 30px;
}
Loading

0 comments on commit aa2b691

Please sign in to comment.